Migriert die weiterhin benötigten Zeitaufgaben einschließlich SOAP-Importen, Betriebsstatus und Wiederanlaufkontrolle. Aktiviert 115 native Spring-Tasks im Docker-Image ohne Cron-Daemon oder PHP und dokumentiert die entfallenen HHA-Aufrufe. Ergänzt PHP-/UI-Testwerkzeuge und aktualisiert den Projektstand auf Version 0.2.0.
1359 lines
135 KiB
Java
1359 lines
135 KiB
Java
package de.stadtbote.votian;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import de.stadtbote.votian.database.DatabaseService;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
import org.springframework.dao.DataAccessResourceFailureException;
|
|
import org.springframework.mock.web.MockHttpSession;
|
|
import org.springframework.security.core.context.SecurityContext;
|
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
|
import org.springframework.test.web.servlet.MockMvc;
|
|
import org.springframework.test.web.servlet.MvcResult;
|
|
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.function.Supplier;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.mockito.ArgumentMatchers.*;
|
|
import static org.mockito.Mockito.*;
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
|
|
|
@SpringBootTest(properties={"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"})
|
|
@AutoConfigureMockMvc
|
|
class ApiSecurityTest {
|
|
@Autowired MockMvc mvc;
|
|
@Autowired ObjectMapper json;
|
|
@MockitoBean DatabaseService database;
|
|
@MockitoBean LoginActivity loginActivity;
|
|
|
|
@Test void cronReviewAndStatusRequireSessionRightsAndCsrf() throws Exception {
|
|
mvc.perform(get("/api/administration/cron/database-status?month=2026-10")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(post("/api/administration/cron/mysql-status/review").session(session).contentType("application/json").content("{\"reviewed\":true}")).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-cron-rights"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-cron-rights","emp_rights","00000000")));
|
|
var denied=authenticated("no-cron-rights","pw");var token=csrf(denied);
|
|
mvc.perform(get("/api/administration/cron/database-status?month=2026-10").session(denied)).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/cron/mysql-status/review").session(denied).header(token.header(),token.token()).contentType("application/json").content("{\"reviewed\":true}")).andExpect(status().isForbidden());
|
|
verify(database,never()).cronOperation(anyString(),any());
|
|
}
|
|
|
|
@Test void customerAddressPropagationRequiresSessionRightsAndCsrf() throws Exception {
|
|
String base="/api/master/customers/1/address-propagation";
|
|
var anon=csrf(null);var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-propagation"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-propagation","emp_rights","00000000")));
|
|
var denied=authenticated("no-propagation","pw");var token=csrf(denied);
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());mvc.perform(get(base).session(denied)).andExpect(status().isForbidden());
|
|
for(String path:List.of(base,base+"/preview")){
|
|
mvc.perform(post(path).session(anon.session()).header(anon.header(),anon.token()).contentType("application/json").content("{}")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(denied).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void informationRequiresSessionAndNoticesRequireCsrf() throws Exception {
|
|
for(String path:List.of("/api/account/info","/api/account/info/search?q=abc","/api/customer-portal/info"))mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(put("/api/account/info/notices").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).employeeActivityWriteTransaction(any());
|
|
}
|
|
|
|
@Test void employeeStatusAndInboxRequireSessionAndCsrf() throws Exception {
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String path:List.of("/api/account/activity","/api/account/inbox"))mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
mvc.perform(put("/api/account/activity").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/account/inbox/read").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).employeeActivityWriteTransaction(any());verify(database,never()).employeeInboxWriteTransaction(anyString(),any());
|
|
}
|
|
|
|
@Test void loginActivityRunsOnlyAfterSuccessfulAuthentication() throws Exception {
|
|
login(csrf(null),"existing-user","wrong-password",401);
|
|
verifyNoInteractions(loginActivity);
|
|
authenticated("existing-user","existing-password");
|
|
verify(loginActivity).record(any(LegacyUser.class));
|
|
}
|
|
@Test void failedLoginRecordingInvalidatesAuthenticatedSession() throws Exception {
|
|
doThrow(new org.springframework.dao.DataAccessResourceFailureException("PRIVATE_DATABASE_DETAIL")).when(loginActivity).record(any(LegacyUser.class));
|
|
var result=login(csrf(null),"existing-user","existing-password",503);
|
|
assertThat(result.getRequest().getSession(false)).isNull();
|
|
assertThat(result.getResponse().getContentAsString()).contains("nicht vollständig gespeichert").doesNotContain("PRIVATE_DATABASE_DETAIL");
|
|
mvc.perform(get("/api/me")).andExpect(status().isUnauthorized());
|
|
}
|
|
|
|
@Test void masterExportTransferRequiresSessionCsrfAndExportPermission() throws Exception {
|
|
String base="/api/data-transfer/master-export-files/transfer";
|
|
var anon=csrf(null);var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-export-transfer"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-export-transfer","emp_rights","00000000")));
|
|
var denied=authenticated("no-export-transfer","pw");var token=csrf(denied);
|
|
for(String path:List.of(base,base+"/remote",base+"/1")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(path).session(denied)).andExpect(status().isForbidden());
|
|
}
|
|
for(String path:List.of(base,base+"/00000000-0000-0000-0000-000000000001/resume",base+"/00000000-0000-0000-0000-000000000001/cancel",base+"/00000000-0000-0000-0000-000000000001/received")){
|
|
mvc.perform(post(path).session(anon.session()).header(anon.header(),anon.token()).contentType("application/json").content("{}")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(denied).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).documentOperation(any());
|
|
verify(database,never()).customerExportFileWriteTransaction(any());
|
|
}
|
|
|
|
@Test void deliveryEvidenceRequiresCurrentOrderReadPermission() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-evidence"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-evidence","emp_rights","00000000")));
|
|
var denied=authenticated("no-evidence","pw");
|
|
for(String path:List.of("/api/orders/101/delivery-evidence","/api/orders/101/delivery-evidence/201/images/signature","/api/orders/101/delivery-evidence/201/images/pic-1")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());mvc.perform(get(path).session(denied)).andExpect(status().isForbidden());
|
|
}verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void confirmedOrderMailRequiresEntrySessionAndCsrf() throws Exception {
|
|
var anon=csrf(null);var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-request-mail"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-request-mail","emp_rights","00000000")));
|
|
var denied=authenticated("no-request-mail","pw");var token=csrf(denied);String base="/api/orders/entry/request-mail/00000000-0000-0000-0000-000000000001";
|
|
for(String path:List.of(base,"/api/orders/entry/request-mail/recent",base+"/1/status","/api/orders/1/mail","/api/orders/1/mail/status","/api/orders/1/mail/preview/00000000-0000-0000-0000-000000000001/pdf/0")){mvc.perform(get(path)).andExpect(status().isUnauthorized());mvc.perform(get(path).session(denied)).andExpect(status().isForbidden());}
|
|
for(String path:List.of(base+"/1/preview",base+"/1/send","/api/orders/1/mail/preview","/api/orders/1/mail/send")){
|
|
mvc.perform(post(path).session(anon.session()).header(anon.header(),anon.token()).contentType("application/json").content("{}")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(denied).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
|
|
@Test void statusMailLookupRequiresEntryPermission() throws Exception {
|
|
mvc.perform(get("/api/orders/entry/status-mail?payerId=41&payment=INVOICE&vehicleTypeId=2")).andExpect(status().isUnauthorized());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-entry-mail"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-entry-mail","emp_rights","00000000")));
|
|
var denied=authenticated("no-entry-mail","pw");
|
|
mvc.perform(get("/api/orders/entry/status-mail?payerId=41&payment=INVOICE&vehicleTypeId=2").session(denied)).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void longhaulReturnStatusRequiresSessionAndOrderReadRight() throws Exception {
|
|
mvc.perform(get("/api/orders/1/bwv-return")).andExpect(status().isUnauthorized());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-orders-return"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-orders-return","emp_rights","00000000")));
|
|
var denied=authenticated("no-orders-return","pw");
|
|
mvc.perform(get("/api/orders/1/bwv-return").session(denied)).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void courierAssignmentRequiresSessionCsrfAndBothOrderRights() throws Exception {
|
|
var anon=csrf(null);var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("list-only-courier"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","list-only-courier","emp_rights","00001000")));
|
|
var denied=authenticated("list-only-courier","pw");var token=csrf(denied);
|
|
for(String path:List.of("/api/orders/1/courier-assignment","/api/orders/1/courier-assignment/preview","/api/orders/1/courier-assignment/candidates")){
|
|
mvc.perform(post(path).session(anon.session()).header(anon.header(),anon.token()).contentType("application/json").content("{}")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(denied).header(token.header(),token.token()).contentType("application/json").content(path.endsWith("/candidates")?"{\"mode\":\"CURRENT\",\"radius\":0}":"{\"vehicleId\":1}")).andExpect(status().isForbidden());
|
|
}
|
|
mvc.perform(get("/api/orders/1/courier-assignment/vehicles/1/orders?courierId=1")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/orders/1/courier-assignment/vehicles/1/orders?courierId=1").session(denied)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/1/courier-assignment/classic-candidates")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/orders/1/courier-assignment/classic-candidates").session(denied)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/1/courier-assignment/candidates/options")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/orders/1/courier-assignment/candidates/options").session(denied)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/1/courier-assignment/vehicles")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/orders/1/courier-assignment/vehicles").session(denied)).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void mediationRequiresSessionCsrfAndBothOrderRights() throws Exception {
|
|
var anonymous=csrf(null);var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/orders/1/mediation")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/orders/1/mediation/timeout")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/orders/1/mediation/automatic")).andExpect(status().isUnauthorized());
|
|
for(String rights:List.of("00001000","00000001")){
|
|
String name="mediation-"+rights;
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq(name),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account",name,"emp_rights",rights)));
|
|
var denied=authenticated(name,"pw");var token=csrf(denied);
|
|
mvc.perform(get("/api/orders/1/mediation").session(denied)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/1/mediation/timeout").session(denied)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/1/mediation/automatic").session(denied)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/orders/1/mediation","/api/orders/1/mediation/preview")){
|
|
mvc.perform(post(path).session(anonymous.session()).header(anonymous.header(),anonymous.token()).contentType("application/json").content("{}")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(denied).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
}
|
|
verify(database,never()).mediationWriteTransaction(any());
|
|
}
|
|
|
|
@Test void rankingBatchRequiresSessionCsrfAndBothOrderRights() throws Exception {
|
|
var anon=csrf(null);var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("list-only-ranking"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","list-only-ranking","emp_rights","00001000")));
|
|
var denied=authenticated("list-only-ranking","pw");var csrf=csrf(denied);
|
|
for(String path:List.of("/api/orders/ranking-batch","/api/orders/ranking-batch/preview")){
|
|
mvc.perform(post(path).session(anon.session()).header(anon.header(),anon.token()).contentType("application/json").content("{}")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(denied).header(csrf.header(),csrf.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void stationSortingRequiresSessionCsrfAndRights() throws Exception {
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/orders/1/station-order")).andExpect(status().isUnauthorized());
|
|
mvc.perform(put("/api/orders/1/station-order").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/orders/1/station-order/preview").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/1/station-order").session(session)).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void masterDocumentsRequireSessionAndCsrf() throws Exception {
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/postal-neighbour-import")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/zone-job-import/1")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/allergo-import/1")).andExpect(status().isUnauthorized());
|
|
for(String path:List.of("/api/allergo-import/1","/api/allergo-import/1/preview"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/tour-csv-import/1")).andExpect(status().isUnauthorized());
|
|
for(String path:List.of("/api/tour-csv-import/1","/api/tour-csv-import/1/preview","/api/tour-csv-import/1?format=DHL","/api/tour-csv-import/1/preview?format=DHL","/api/tour-csv-import/1?format=TIMS","/api/tour-csv-import/1/preview?format=TIMS"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/zone-job-import/1","/api/zone-job-import/1/preview","/api/zone-job-import/1?format=BMW","/api/zone-job-import/1/preview?format=BMW"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/postal-neighbour-import/source?kind=general&object=1&name=a&revision=b")).andExpect(status().isUnauthorized());
|
|
for(String path:List.of("/api/postal-neighbour-import","/api/postal-neighbour-import/preview"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/data-transfer")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/data-transfer/objects?kind=orders")).andExpect(status().isUnauthorized());
|
|
for(String kind:List.of("customers","couriers","vehicles","orders","employees","general")){
|
|
String remote="/api/remote-transfers/"+kind+"/1";
|
|
mvc.perform(get(remote)).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(remote+"/files?alias=TEST&protocol=SFTP")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(remote).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(remote+"/11111111-1111-4111-8111-111111111111/resume").session(session)).andExpect(status().isForbidden());
|
|
String path="/api/master-documents/"+kind+"/1";
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(path+"/download?name=test.txt")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(path+"/import-source?format=ZONE&name=test.csv&revision=none")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path+"/operations/11111111-1111-4111-8111-111111111111/resume").session(session)).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).documentOperation(any());
|
|
verify(database,never()).documentReceiptTransaction(any());
|
|
}
|
|
|
|
@Test void masterDirectoryExportsRequireSessionCsrfAndSeparateExportRights() throws Exception {
|
|
var anon=csrf(null);
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("list-only"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","list-only","emp_rights","11")));
|
|
var session=authenticated("list-only","pw");var token=csrf(session);
|
|
for(String kind:List.of("customers","couriers")){
|
|
String path="/api/master-lists/"+kind+"/csv";
|
|
mvc.perform(post(path).session(anon.session()).header(anon.header(),anon.token()).contentType("application/json").content("{}")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void headquartersSettingsRequireSessionCsrfAndBothEditingPermissions() throws Exception {
|
|
String base="/api/administration/headquarters-settings";
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());
|
|
var anon=csrf(null);var session=authenticated("existing-user","existing-password");
|
|
for(int missing:List.of(47,45)){
|
|
String bits="1".repeat(70);bits=bits.substring(0,missing)+"0"+bits.substring(missing+1);String name="hq-settings-"+missing;
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq(name),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account",name,"emp_rights",bits)));
|
|
var denied=authenticated(name,"pw");var token=csrf(denied);
|
|
for(String suffix:List.of("/HOTLINE/0/preview","/HOTLINE/0","/GLN/8/preview","/DUNS/8")){
|
|
mvc.perform(post(base+suffix).session(anon.session()).header(anon.header(),anon.token()).contentType("application/json").content("{}")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(base+suffix).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(base+suffix).session(denied).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void workspaceScopeReachesControllersWithoutChangingTheSharedSession() throws Exception {
|
|
mvc.perform(get("/api/account/headquarters")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/account/headquarters/8")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(WorkspaceHeadquarters.ACTOR_SQL),eq(11L),eq(12L),eq(7L))).thenReturn(List.of(Map.of("emp_rights","1".repeat(70),"usr_account","existing-user","hq_admin",12,"hq_mnemonic","B")));
|
|
when(database.queryForList(eq("SELECT md_id FROM mandatorheadquarters WHERE hq_id=?"),eq(7L))).thenReturn(List.of(Map.of("md_id",1)));
|
|
when(database.queryForList(eq(WorkspaceHeadquarters.BRANCH_SQL),eq(1L))).thenReturn(List.of(Map.of("id",7,"label","B"),Map.of("id",8,"label","HH")));
|
|
String parameter="SELECT par_id,par_value FROM parameter WHERE emp_id=0 AND hq_id=0 AND par_key=? ORDER BY par_id";
|
|
when(database.queryForList(eq(parameter),eq(CallCenterAccess.EMPLOYEES))).thenReturn(List.of(Map.of("par_id",1,"par_value","|12|")));
|
|
mvc.perform(get("/api/me").session(session).header(WorkspaceHeadquartersFilter.HEADER,"8")).andExpect(status().isOk()).andExpect(jsonPath("$.headquartersId").value(8)).andExpect(jsonPath("$.originalHeadquartersId").value(7));
|
|
mvc.perform(get("/api/customers").session(session).header(WorkspaceHeadquartersFilter.HEADER,"8")).andExpect(status().isOk());
|
|
verify(database).queryForList(contains("FROM customer cs"),eq(8L));
|
|
var saved=(SecurityContext)session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
|
assertThat(((LegacyUser)saved.getAuthentication().getPrincipal()).headquartersId()).isEqualTo(7);
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.headquartersId").value(7));
|
|
mvc.perform(get("/api/account/headquarters/7").session(session).header(WorkspaceHeadquartersFilter.HEADER,"999")).andExpect(status().isOk()).andExpect(jsonPath("$.headquartersId").value(7));
|
|
mvc.perform(post("/api/orders").session(session).header(WorkspaceHeadquartersFilter.HEADER,"8").contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(parameter),eq(CallCenterAccess.EMPLOYEES))).thenReturn(List.of());
|
|
// The HQ administrator still has the ordinary multi-HQ grant after CC24 is removed.
|
|
mvc.perform(get("/api/me").session(session).header(WorkspaceHeadquartersFilter.HEADER,"8")).andExpect(status().isOk()).andExpect(jsonPath("$.headquartersId").value(8));
|
|
when(database.queryForList(eq(WorkspaceHeadquarters.ACTOR_SQL),eq(11L),eq(12L),eq(7L))).thenReturn(List.of(Map.of("emp_rights","1".repeat(10)+"0"+"1".repeat(59),"usr_account","existing-user","hq_admin",12,"hq_mnemonic","B")));
|
|
mvc.perform(get("/api/me").session(session).header(WorkspaceHeadquartersFilter.HEADER,"8")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("WORKSPACE_DENIED"));
|
|
var token=csrf(session);
|
|
mvc.perform(post("/api/orders").session(session).header(token.header(),token.token()).header(WorkspaceHeadquartersFilter.HEADER,"8").contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/account/headquarters/7").session(session).header(WorkspaceHeadquartersFilter.HEADER,"8")).andExpect(status().isOk());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void callCenterRequiresBothEmployeeAndCc24AdministrationRights() throws Exception {
|
|
for(int missing:List.of(3,34)){
|
|
String bits="1".repeat(70);bits=bits.substring(0,missing)+"0"+bits.substring(missing+1);
|
|
String name="cc24-denied-"+missing;
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq(name),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account",name,"emp_rights",bits)));
|
|
var session=authenticated(name,"pw");var csrf=csrf(session);
|
|
mvc.perform(get("/api/administration/employees/20/call-center").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(put("/api/administration/employees/20/call-center").session(session).header(csrf.header(),csrf.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).callCenterWriteTransaction(any());
|
|
}
|
|
|
|
@Test void personalListPreferencesRequireSessionAndCsrf() throws Exception {
|
|
for(String path:List.of("/api/administration/employees/20/call-center","/api/administration/employees/20/stock-rights","/api/account/order-columns","/api/administration/employees/20/order-columns","/api/account/master-list-columns/customers","/api/administration/employees/20/master-list-columns/couriers","/api/account/list-preferences","/api/administration/employees/20/list-preferences","/api/account/parallel-lists","/api/administration/employees/20/parallel-lists","/api/account/cost-center-columns","/api/administration/employees/20/cost-center-columns")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(put(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-employees"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-employees","emp_rights","1")));
|
|
var denied=authenticated("no-employees","pw");mvc.perform(get("/api/administration/employees/20/list-preferences").session(denied)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/administration/employees/20/stock-rights").session(denied)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/administration/employees/20/parallel-lists").session(denied)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders?personal=true&listStatus=99").session(authenticated("existing-user","existing-password"))).andExpect(status().isBadRequest());
|
|
mvc.perform(get("/api/orders?listStatus=8").session(authenticated("existing-user","existing-password"))).andExpect(status().isBadRequest());
|
|
mvc.perform(get("/api/orders/list")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/orders/list").session(denied)).andExpect(status().isForbidden());
|
|
var docAnonymous=csrf(null);var docDenied=csrf(denied);
|
|
mvc.perform(post("/api/orders/list/document-presence").session(docAnonymous.session()).header(docAnonymous.header(),docAnonymous.token()).contentType("application/json").content("{\"customers\":[1]}")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post("/api/orders/list/document-presence").session(denied).header(docDenied.header(),docDenied.token()).contentType("application/json").content("{\"customers\":[1]}")).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/list/options")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/orders/list/options").session(denied)).andExpect(status().isForbidden());
|
|
var listSession=authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/orders/list").param("filters","{\"unknown\":\"1\"}").session(listSession)).andExpect(status().isBadRequest());
|
|
mvc.perform(get("/api/orders").param("filters","{\"future\":\"1\"}").session(listSession)).andExpect(status().isBadRequest());
|
|
mvc.perform(get("/api/orders/export.csv").param("filters","{\"future\":\"1\"}").session(listSession)).andExpect(status().isBadRequest());
|
|
mvc.perform(get("/api/administration/employees/20/order-columns").session(denied)).andExpect(status().isForbidden());
|
|
verify(database,never()).employeePreferencesWriteTransaction(any());
|
|
}
|
|
|
|
@Test void batchInvoiceRefreshRequiresSessionCsrfAndBothOrderRights() throws Exception {
|
|
String path="/api/orders/1/invoice-items";
|
|
var anonymous=csrf(null);mvc.perform(post(path).session(anonymous.session()).header(anonymous.header(),anonymous.token()).contentType("application/json").content("{}")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(put(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-refresh"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-refresh","emp_rights","1")));
|
|
var denied=authenticated("no-refresh","pw");var token=csrf(denied);
|
|
mvc.perform(post(path).session(denied).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(put(path).session(denied).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void batchEntryRequiresSessionCsrfAndEntryRight() throws Exception {
|
|
mvc.perform(get("/api/order-batches?costCenterId=1")).andExpect(status().isUnauthorized());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-entry"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-entry","emp_rights","1")));
|
|
var denied=authenticated("no-entry","pw");var token=csrf(denied);
|
|
mvc.perform(get("/api/order-batches?costCenterId=1").session(denied)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/order-batches","/api/order-batches/preview","/api/orders/entry/cost-centers/search")) {
|
|
mvc.perform(post(path).session(denied).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(denied).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).orderBatchWriteTransaction(anyString(),any());
|
|
}
|
|
|
|
@Test void fleetMapNeedsItsOwnRightAndOrderReadsNeedBothRights() throws Exception {
|
|
mvc.perform(get("/api/administration/fleet-map")).andExpect(status().isUnauthorized());
|
|
var session=login(csrf(null),"existing-user","existing-password",200).getRequest().getSession(false);
|
|
for(String path:List.of("","/places","/orders/1","/stocks/1/position","/orders/1/stations/1/position","/orders/1/assignment/1","/couriers/1/location-check"))
|
|
mvc.perform(get("/api/administration/fleet-map"+path).session((MockHttpSession)session)).andExpect(status().isForbidden());
|
|
}
|
|
|
|
@Test void mapOnlyAccountGetsConfiguredTilesWithoutOrderData() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("map-user"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","map-user","emp_rights","0".repeat(19)+"1")));
|
|
when(database.queryForList(eq("SELECT NOW() AS at"))).thenReturn(List.of(Map.of("at","2026-10-15 12:00:00")));
|
|
when(database.queryForList(eq("SELECT hq_name,hq_gps_lat,hq_gps_long FROM headquarters WHERE hq_id=?"),eq(7L))).thenReturn(List.of(Map.of("hq_name","Test")));
|
|
var session=authenticated("map-user","pw");
|
|
mvc.perform(get("/api/administration/fleet-map").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.tiles").value("https://tile.openstreetmap.org/{z}/{x}/{y}.png")).andExpect(jsonPath("$.orders").value(false)).andExpect(jsonPath("$.items").isEmpty());
|
|
mvc.perform(get("/api/administration").session(session)).andExpect(jsonPath("$.fleetMap").value(true));
|
|
mvc.perform(get("/api/administration/fleet-map/orders/1").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/administration/fleet-map/orders/1/stations/1/position").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/administration/fleet-map/orders/1/assignment/1").session(session)).andExpect(status().isForbidden());
|
|
var csrf=csrf(session);
|
|
mvc.perform(get("/api/administration/fleet-map/couriers/1/location-check").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/fleet-map/couriers/1/location-check").session(session).header(csrf.header(),csrf.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/fleet-map/orders/1/assignment/1").session(session).header(csrf.header(),csrf.token()).contentType("application/json").content("{}" )).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
record Csrf(MockHttpSession session, String token, String header) {}
|
|
|
|
@Test void employeeChangesRequireSessionCsrfAndCurrentEmployeeRight() throws Exception {
|
|
mvc.perform(get("/api/administration/employees")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String part:List.of("profile","rights"))mvc.perform(put("/api/administration/employees/20/"+part).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
var token=csrf(session);
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenReturn(List.of(Map.of("usr_account","existing-user","emp_rights","1")));
|
|
mvc.perform(get("/api/administration/employees").session(session)).andExpect(status().isForbidden());
|
|
for(String part:List.of("profile","rights"))mvc.perform(put("/api/administration/employees/20/"+part).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).employeeWriteTransaction(any());
|
|
}
|
|
|
|
@Test void sessionsLoseRevokedRightsAndDeletedAccountsCannotContinue() throws Exception {
|
|
var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenReturn(List.of(Map.of("usr_account","existing-user","emp_rights","0".repeat(70))));
|
|
mvc.perform(get("/api/orders").session(session)).andExpect(status().isForbidden());
|
|
// The shared session principal itself remains untouched by a request in another tab.
|
|
var context=(SecurityContext)session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
|
assertThat(((LegacyUser)context.getAuthentication().getPrincipal()).hasRight(4)).isTrue();
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenReturn(List.of());
|
|
mvc.perform(get("/api/orders").session(session)).andExpect(status().isUnauthorized());
|
|
assertThat(session.isInvalid()).isTrue();
|
|
}
|
|
|
|
@Test void unavailableRightsCheckFailsClosedAndNewGrantsRequireLogin() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("limited"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","limited","emp_rights","1")));
|
|
var session=authenticated("limited","pw");
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenReturn(List.of(Map.of("usr_account","limited","emp_rights","1".repeat(70))));
|
|
mvc.perform(get("/api/administration/employees").session(session)).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenThrow(new DataAccessResourceFailureException("connection failed"));
|
|
mvc.perform(get("/api/customers").session(session)).andExpect(status().isServiceUnavailable());
|
|
}
|
|
|
|
@Test void changedCredentialsInvalidateAnExistingSession() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("existing-user"),eq("existing-password"),eq("existing-password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","existing-user","emp_rights","111111111111","credential_stamp","original")));
|
|
var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenReturn(List.of(Map.of("usr_account","existing-user","emp_rights","111111111111","credential_stamp","changed")));
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isUnauthorized());assertThat(session.isInvalid()).isTrue();
|
|
}
|
|
@Test void warehouseAccountsRequireSessionCsrfAndEmployeeRight() throws Exception {
|
|
mvc.perform(get("/api/administration/warehouse-users")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");var token=csrf(session);
|
|
for(String path:List.of("/save","/108/delete"))mvc.perform(post("/api/administration/warehouse-users"+path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String path:List.of("/108/password","/108/stock-rights"))mvc.perform(put("/api/administration/warehouse-users"+path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenReturn(List.of(Map.of("usr_account","existing-user","emp_rights","1")));
|
|
mvc.perform(get("/api/administration/warehouse-users").session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/save","/108/delete"))mvc.perform(post("/api/administration/warehouse-users"+path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String path:List.of("/108/password","/108/stock-rights"))mvc.perform(put("/api/administration/warehouse-users"+path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).warehouseUserWriteTransaction(any());
|
|
}
|
|
|
|
@Test void accountMutationsRequireSessionCsrfAndEmployeeRight() throws Exception {
|
|
var session=authenticated("existing-user","existing-password");var token=csrf(session);
|
|
mvc.perform(post("/api/administration/employees").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/employees/102/delete").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(put("/api/administration/employees/102/password").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenReturn(List.of(Map.of("usr_account","existing-user","emp_rights","1")));
|
|
mvc.perform(post("/api/administration/employees").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/employees/102/delete").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(put("/api/administration/employees/102/password").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).employeeWriteTransaction(any());
|
|
}
|
|
|
|
@Test void anActivatedSecondFactorCannotBeSkippedOrSuppliedThroughAHeader() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("existing-user"),eq("existing-password"),eq("existing-password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","existing-user","emp_rights","111111111111","usr_totp_activated",1,"usr_totp_secret",LegacyTotpTest.KEY)));
|
|
var token=csrf(null);login(token,"existing-user","existing-password",401);
|
|
mvc.perform(get("/api/me").session(token.session())).andExpect(status().isUnauthorized());
|
|
mvc.perform(post("/api/login").session(token.session()).header(token.header(),token.token()).header("otp",LegacyTotp.code(LegacyTotpTest.KEY,java.time.Instant.now().getEpochSecond())).contentType("application/x-www-form-urlencoded").param("username","existing-user").param("password","existing-password")).andExpect(status().isUnauthorized());
|
|
verify(database,never()).authenticationWriteTransaction(any());
|
|
}
|
|
|
|
@Test void ownAccountIsAvailableWithoutEmployeeRightsButCannotSelectAnotherPerson() throws Exception {
|
|
var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenReturn(List.of(Map.of("usr_account","existing-user","emp_rights","0".repeat(70))));
|
|
when(database.queryForList(eq(EmployeeAdministration.PERSON_SQL+" AND employee.emp_id=? AND user.usr_id=? AND user.hq_id=?"),eq(12L),eq(11L),eq(7L))).thenReturn(List.of(Map.of("emp_id",12,"usr_id",11,"hq_id",7,"emp_rights","0".repeat(70),"emp_headquarters","7","usr_name","Test","usr_firstname","Mara","usr_account","existing-user")));
|
|
when(database.queryForList(eq("SELECT md_id FROM mandatorheadquarters WHERE hq_id=? AND md_id>0"),eq(7L))).thenReturn(List.of(Map.of("md_id",1)));
|
|
when(database.queryForList(startsWith("SELECT headquarters.hq_id AS id,headquarters.hq_mnemonic AS label"),eq(1L))).thenReturn(List.of(Map.of("id",7,"label","B","administrator",99)));
|
|
when(database.queryForList(eq("SELECT md_id FROM mandatorheadquarters WHERE hq_id=?"),eq(7L))).thenReturn(List.of(Map.of("md_id",1)));
|
|
when(database.queryForList(eq("SELECT "+LegacyCredentialState.EXPRESSION+" AS credential_stamp,usr_password_modify,usr_totp_activated FROM user WHERE usr_id=?"),eq(11L))).thenReturn(List.of(Map.of("credential_stamp","server-only","usr_totp_activated",0)));
|
|
mvc.perform(get("/api/account?employee=999").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.profile.id").value(12)).andExpect(jsonPath("$.canPassword").value(true)).andExpect(jsonPath("$.canDelete").value(false)).andExpect(jsonPath("$.credential_stamp").doesNotExist());
|
|
mvc.perform(get("/api/account/two-factor").session(session)).andExpect(status().isOk()).andExpect(header().string("Cache-Control",org.hamcrest.Matchers.containsString("no-store"))).andExpect(jsonPath("$.canSetup").value(false)).andExpect(jsonPath("$.secret").doesNotExist());
|
|
mvc.perform(get("/api/administration/employees/12/access").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/administration/employees/12/history").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/administration/employees/12/two-factor").session(session)).andExpect(status().isForbidden());
|
|
var token=csrf(session);
|
|
mvc.perform(post("/api/administration/employees/12/two-factor/reset").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).employeeWriteTransaction(any());
|
|
}
|
|
@Test void selfServiceAndTwoFactorMutationsRequireSessionAndCsrf() throws Exception {
|
|
mvc.perform(get("/api/administration/employees/12/history")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/account")).andExpect(status().isUnauthorized());mvc.perform(get("/api/account/two-factor")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String part:List.of("profile","password"))mvc.perform(put("/api/account/"+part).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String part:List.of("setup","activate","cancel","reset"))mvc.perform(post("/api/account/two-factor/"+part).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/employees/12/two-factor/reset").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).employeeWriteTransaction(any());
|
|
}
|
|
|
|
@Test void customerLoginIsConfinedToCustomerPathsAndRevokedOnChange() throws Exception {
|
|
mvc.perform(get("/api/customer-portal")).andExpect(status().isUnauthorized());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.CUSTOMER_CANDIDATE_SQL),eq("customer-test"),eq("isolated-password"),eq("isolated-password"))).thenReturn(List.of(Map.of("usr_id",31,"emp_id",32,"hq_id",7)));
|
|
var row=Map.<String,Object>of("usr_id",31,"emp_id",32,"hq_id",7,"usr_account","customer-test","emp_rights","1".repeat(70),"credential_stamp","customer-stamp");
|
|
when(database.queryForList(eq(CustomerPortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of(row));
|
|
var session=authenticated("customer-test","isolated-password");var token=csrf(session);
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.accountType").value(2)).andExpect(jsonPath("$.roles[0]").value("CUSTOMER")).andExpect(jsonPath("$.capabilities.readOrders").value(false)).andExpect(jsonPath("$.capabilities.writeCustomers").value(false)).andExpect(jsonPath("$.credential_stamp").doesNotExist());
|
|
for(String path:List.of("/api/account","/api/administration","/api/orders","/api/customers","/api/invoices","/api/warehouse-portal","/api/courier-portal","/api/account/headquarters"))mvc.perform(get(path).session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/orders/entry","/api/administration/warehouse-users/save","/api/administration/warehouse-movements/save"))mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/customer-portal/entry","/api/customer-portal/entry/quote","/api/customer-portal/query","/api/customer-portal/detail","/api/customer-portal/centers/preview","/api/customer-portal/centers/save","/api/customer-portal/employees/query","/api/customer-portal/employees/save","/api/customer-portal/two-factor/setup","/api/customer-portal/two-factor/activate","/api/customer-portal/two-factor/reset","/api/customer-portal/two-factor/cancel","/api/customer-portal/statement","/api/customer-portal/statement/detail","/api/customer-portal/orders/100/cancellation","/api/customer-portal/orders/100/texts","/api/customer-portal/orders/100/texts/preview","/api/customer-portal/orders/100/edit","/api/customer-portal/orders/100/edit/preview","/api/customer-portal/statistics/query","/api/customer-portal/statistics/download","/api/customer-portal/exports/preview","/api/customer-portal/exports/download","/api/customer-portal/exports/save","/api/customer-portal/export-storage","/api/customer-portal/export-storage/00000000-0000-0000-0000-000000000001/resume","/api/customer-portal/export-storage/00000000-0000-0000-0000-000000000001/cancel","/api/customer-portal/export-removal","/api/customer-portal/export-removal/00000000-0000-0000-0000-000000000001/resume","/api/customer-portal/export-removal/00000000-0000-0000-0000-000000000001/restore")){
|
|
mvc.perform(post(path).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
for(String method:List.of("PUT","DELETE","PATCH"))mvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request(org.springframework.http.HttpMethod.valueOf(method),"/api/customer-portal/detail").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String header:List.of("X-Workspace-Headquarters","X-Order-Headquarters"))mvc.perform(get("/api/me").session(session).header(header,"7")).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(CustomerPortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of(Map.of("credential_stamp","changed")));
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isUnauthorized());
|
|
}
|
|
|
|
@Test void courierLoginCannotReachOtherWorkspacesAndIsRevokedOnChange() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.COURIER_CANDIDATE_SQL),eq("courier-test"),eq("isolated-password"),eq("isolated-password"))).thenReturn(List.of(Map.of("usr_id",21,"hq_id",7)));
|
|
var row=Map.<String,Object>of("usr_id",21,"hq_id",7,"cr_id",700,"usr_account","courier-test","emp_rights","1".repeat(70),"credential_stamp","courier-stamp");
|
|
when(database.queryForList(eq(CourierPortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of(row));
|
|
var session=authenticated("courier-test","isolated-password");var token=csrf(session);
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.accountType").value(3)).andExpect(jsonPath("$.roles[0]").value("COURIER")).andExpect(jsonPath("$.capabilities.readOrders").value(false)).andExpect(jsonPath("$.capabilities.writeCustomers").value(false));
|
|
for(String path:List.of("/api/account","/api/administration","/api/orders","/api/customers","/api/invoices","/api/warehouse-portal","/api/account/headquarters","/api/administration/warehouse-users"))mvc.perform(get(path).session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/orders/entry","/api/administration/warehouse-users/save","/api/administration/warehouse-movements/save"))mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/courier-portal/query","/api/courier-portal/remark","/api/courier-portal/password","/api/courier-portal/history","/api/courier-portal/bourse/query","/api/courier-portal/bourse/detail","/api/courier-portal/disposition/job/remark"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/courier-portal/disposition/job?vehicle=101&day=2026-10-19&job=20")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/courier-portal/disposition/job?vehicle=101&day=2026-10-19&job=20").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/courier-portal/disposition/job/remark").session(session).header(token.header(),token.token()).contentType("application/json").content("{\"requestId\":\"00000000-0000-0000-0000-000000000001\",\"value\":\"test\"}")).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/courier-portal/disposition/rollcard","/api/courier-portal/disposition/rollcard/pdf")){
|
|
mvc.perform(get(path+"?vehicle=101&day=2026-10-19&revision=fixture")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(path+"?vehicle=101&day=2026-10-19&revision=fixture").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
String courierMail="/api/courier-portal/disposition/101/rollcard/mail",courierMailToken="00000000-0000-0000-0000-000000000001";
|
|
for(String suffix:List.of("","/"+courierMailToken,"/"+courierMailToken+"/page")){
|
|
mvc.perform(get(courierMail+suffix+"?day=2026-10-19&page=0")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(courierMail+suffix+"?day=2026-10-19&page=0").session(session)).andExpect(status().isForbidden());
|
|
}
|
|
for(String suffix:List.of("/preview","/"+courierMailToken)){
|
|
mvc.perform(post(courierMail+suffix).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(courierMail+suffix).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).courierRollcardMailWriteTransaction(any());
|
|
mvc.perform(get("/api/courier-portal/disposition/planning?vehicle=101&day=2026-10-19")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/courier-portal/disposition/planning?vehicle=101&day=2026-10-19").session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/courier-portal/disposition/planning","/api/courier-portal/disposition/planning/preview")){
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).courierPlanningWriteTransaction(any());
|
|
|
|
mvc.perform(get("/api/courier-portal/disposition/handover?vehicle=101&day=2026-10-19&job=20")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/courier-portal/disposition/handover?vehicle=101&day=2026-10-19&job=20").session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/courier-portal/disposition/handover","/api/courier-portal/disposition/handover/preview")){
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
mvc.perform(get("/api/courier-portal/disposition/replanning?vehicle=101&day=2026-10-19&job=10")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/courier-portal/disposition/replanning?vehicle=101&day=2026-10-19&job=10").session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/courier-portal/disposition/replanning","/api/courier-portal/disposition/replanning/preview")){
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).courierReplanningWriteTransaction(any());
|
|
mvc.perform(get("/api/courier-portal/disposition/cards?vehicle=101&day=2026-10-19")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/courier-portal/disposition/cards?vehicle=101&day=2026-10-19").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/courier-portal/disposition/cards").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/administration/vehicle-disposition/cards?vehicles=101&day=2026-10-19").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/administration/vehicle-disposition/unplanned-cards?day=2026-10-19").session(session)).andExpect(status().isForbidden());
|
|
|
|
mvc.perform(get("/api/courier-portal/disposition/job-search?q=20")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/courier-portal/disposition/job-search?q=20").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/courier-portal/disposition/job-search").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/courier-portal/disposition?vehicle=101").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/courier-portal/disposition")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/courier-portal/disposition").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/courier-portal/disposition").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String header:List.of("X-Workspace-Headquarters","X-Order-Headquarters"))mvc.perform(get("/api/me").session(session).header(header,"7")).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(CourierPortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of(Map.of("credential_stamp","changed")));mvc.perform(get("/api/me").session(session)).andExpect(status().isUnauthorized());
|
|
verify(database,never()).courierPortalWriteTransaction(any());verify(database,never()).courierDispositionWriteTransaction(any());
|
|
}
|
|
|
|
@Test void warehouseLoginIsConfinedToItsPortalAndRevokedWithTheAccount() throws Exception {
|
|
var row=Map.<String,Object>of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","warehouse-test","emp_rights","1".repeat(70),"credential_stamp","portal-stamp");
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.PORTAL_AUTH_SQL),eq("warehouse-test"),eq("isolated-password"),eq("isolated-password"))).thenReturn(List.of(row));
|
|
when(database.queryForList(eq(WarehousePortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of(row));
|
|
var session=authenticated("warehouse-test","isolated-password");var token=csrf(session);
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.accountType").value(4)).andExpect(jsonPath("$.roles[0]").value("WAREHOUSE")).andExpect(jsonPath("$.capabilities.readOrders").value(false)).andExpect(jsonPath("$.capabilities.writeCustomers").value(false)).andExpect(jsonPath("$.credential_stamp").doesNotExist());
|
|
for(String path:List.of("/api/account","/api/administration","/api/orders","/api/customers","/api/invoices","/api/administration/warehouse-details/10","/api/account/headquarters","/api/administration/articles/options","/api/administration/article-groups","/api/account/article-columns","/api/administration/warehouse-users"))mvc.perform(get(path).session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/administration/warehouses/save","/api/administration/warehouses/preview","/api/administration/warehouse-details/save","/api/administration/warehouse-journal/10/columns","/api/administration/warehouse-journal/csv","/api/administration/warehouse-inventory/csv","/api/orders/entry","/api/administration/warehouse-users/save"))mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String method:List.of("PUT","DELETE","PATCH"))mvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request(org.springframework.http.HttpMethod.valueOf(method),"/api/administration/warehouse-movements/save").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String headerName:List.of("X-Workspace-Headquarters","X-Order-Headquarters"))mvc.perform(get("/api/me").session(session).header(headerName,"7")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/warehouse-movements/save").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).warehouseMovementTransaction(anyBoolean(),any());verify(database,never()).warehouseWriteTransaction(any());
|
|
when(database.queryForList(eq(WarehousePortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of(Map.of("credential_stamp","changed")));
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isUnauthorized());
|
|
when(database.queryForList(eq(WarehousePortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of(row));
|
|
var second=authenticated("warehouse-test","isolated-password");when(database.queryForList(eq(WarehousePortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of());mvc.perform(get("/api/me").session(second)).andExpect(status().isUnauthorized());
|
|
}
|
|
|
|
@Test void transferLoginIsConfinedToFixedFilesAndRevokedOnMappingChange() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.TRANSFER_CANDIDATE_SQL),eq("transfer-test"),eq("isolated-password"),eq("isolated-password"))).thenReturn(List.of(Map.of("usr_id",31,"hq_id",7)));
|
|
var row=Map.<String,Object>of("usr_id",31,"emp_id",0,"hq_id",7,"usr_type",101,"usr_account","transfer-test","emp_rights","","credential_stamp","transfer-stamp");
|
|
when(database.queryForList(eq(TransferPortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of(row));
|
|
String config="SELECT par_value FROM parameter WHERE emp_id=0 AND hq_id=0 AND par_key='DATATRANSFER_USERTYPE_SPECIAL'";
|
|
when(database.queryForList(config)).thenReturn(List.of(Map.of("par_value","101,7,TEST,CS,1,fixed/B")));
|
|
var session=authenticated("transfer-test","isolated-password");var token=csrf(session);
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.accountType").value(101)).andExpect(jsonPath("$.roles[0]").value("TRANSFER")).andExpect(jsonPath("$.capabilities.readOrders").value(false)).andExpect(jsonPath("$.capabilities.readTransfers").value(false)).andExpect(jsonPath("$.credential_stamp").doesNotExist());
|
|
for(String path:List.of("/api/account","/api/administration","/api/orders","/api/customers","/api/invoices","/api/warehouse-portal","/api/customer-portal","/api/data-transfer","/api/master-documents/customers/1","/api/master-documents/transfer/31/import-source","/api/remote-transfers/customers/1"))mvc.perform(get(path).session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/orders/entry","/api/administration/warehouses/save","/api/master-documents/customers/1"))mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/master-documents/transfer/31","/api/master-documents/transfer/31/operations/00000000-0000-0000-0000-000000000001/resume","/api/remote-transfers/transfer/31","/api/remote-transfers/transfer/31/00000000-0000-0000-0000-000000000001/resume"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String header:List.of("X-Workspace-Headquarters","X-Order-Headquarters"))mvc.perform(get("/api/me").session(session).header(header,"7")).andExpect(status().isForbidden());
|
|
when(database.queryForList(config)).thenReturn(List.of(Map.of("par_value","101,7,TEST,CS,2,fixed/B")));mvc.perform(get("/api/me").session(session)).andExpect(status().isForbidden());
|
|
when(database.queryForList(config)).thenReturn(List.of(Map.of("par_value","101,7,TEST,CS,1,fixed/B")));
|
|
when(database.queryForList(eq(TransferPortalScope.ACCOUNT_SQL),any(),any(),any())).thenReturn(List.of(Map.of("credential_stamp","changed")));mvc.perform(get("/api/me").session(session)).andExpect(status().isUnauthorized());
|
|
verify(database,never()).documentOperation(any());
|
|
}
|
|
|
|
@BeforeEach void configure() {
|
|
when(database.queryForList(eq(LegacySessionRightsFilter.SQL),any(),any(),any())).thenAnswer(call -> {
|
|
var auth=org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication();
|
|
var u=(LegacyUser)auth.getPrincipal();
|
|
return List.of(Map.of("usr_account",u.username(),"emp_rights",u.rights()));
|
|
});
|
|
when(database.readTransaction(any())).thenAnswer(call -> ((Supplier<?>)call.getArgument(0)).get());
|
|
when(database.checkConnection()).thenReturn(new DatabaseService.ConnectionCheck(true,"CONNECTED","Verbunden"));
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("existing-user"),eq("existing-password"),eq("existing-password")))
|
|
.thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","existing-user","emp_rights","111111111111")));
|
|
}
|
|
|
|
Csrf csrf(MockHttpSession session) throws Exception {
|
|
var request = get("/api/csrf");
|
|
if (session != null) request.session(session);
|
|
MvcResult response = mvc.perform(request).andExpect(status().isOk())
|
|
.andExpect(header().string("Cache-Control", org.hamcrest.Matchers.containsString("no-store")))
|
|
.andReturn();
|
|
var body = json.readTree(response.getResponse().getContentAsString());
|
|
return new Csrf((MockHttpSession) response.getRequest().getSession(false), body.get("token").asText(), body.get("headerName").asText());
|
|
}
|
|
|
|
MvcResult login(Csrf csrf, String username, String password, int expectedStatus) throws Exception {
|
|
// Represents upstream htaccess authentication: VoTiAn must ignore this header.
|
|
return mvc.perform(post("/api/login").session(csrf.session()).header(csrf.header(),csrf.token())
|
|
.with(httpBasic("proxy-fixture","proxy-password"))
|
|
.contentType("application/x-www-form-urlencoded").param("username",username).param("password",password))
|
|
.andExpect(status().is(expectedStatus)).andExpect(header().doesNotExist("WWW-Authenticate")).andReturn();
|
|
}
|
|
|
|
MockHttpSession authenticated(String username, String password) throws Exception {
|
|
return (MockHttpSession) login(csrf(null),username,password,200).getRequest().getSession(false);
|
|
}
|
|
|
|
@Test void crmAndMailMutationsRequireSessionAndCsrf() throws Exception {
|
|
mvc.perform(get("/api/crm/options")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String path:List.of("/api/crm/contacts","/api/crm/targets","/api/appointments/44/notifications/preview","/api/appointments/notifications/test/send"))
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());verify(database,never()).contactWriteTransaction(any());
|
|
}
|
|
|
|
@Test void messagesAndFormMutationsRequireSessionAndCsrf() throws Exception {
|
|
for(String path:List.of("/api/messages/options","/api/forms","/api/newsletters/options","/api/newsletters/mail/options","/api/evaluations/options"))
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String path:List.of("/api/messages/bulletins","/api/messages/preview","/api/messages/send/token","/api/messages/groups","/api/forms/categories","/api/forms/fields","/api/forms/templates","/api/newsletters","/api/newsletters/images","/api/newsletters/12/mail/preview","/api/newsletters/mail/token/send","/api/evaluations","/api/evaluations/12/mail/preview","/api/evaluations/mail/token/send"))
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(put("/api/forms/categories/10/fields").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
var token=csrf(session);
|
|
mvc.perform(get("/api/forms").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/newsletters/options").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/messages/bulletins").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void extrasRequireSessionCsrfAndDedicatedRight() throws Exception {
|
|
mvc.perform(get("/api/administration/extras/options")).andExpect(status().isUnauthorized());
|
|
mvc.perform(post("/api/administration/extras/query").contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
var session=authenticated("existing-user","existing-password");var token=csrf(session);
|
|
for(String path:List.of("options","customers","pzm-comparison/11111111-1111-4111-8111-111111111111","customer-settings/PHOTO/1","customer-settings/SERVICE_PRICE/1","portal-settings/MASK/1","portal-settings/LIST/101","lead-time","routing-settings/PZM","routing-settings/BWV","pzm-zones","recurring/1?customer=1&asOf=2026-10-14"))mvc.perform(get("/api/administration/extras/"+path).session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("query","csv","pzm-comparison","pzm-comparison/11111111-1111-4111-8111-111111111111/next","pzm-comparison/11111111-1111-4111-8111-111111111111/csv","templates/preview","templates","customer-settings/preview","customer-settings","portal-settings/preview","portal-settings","lead-time/preview","lead-time","routing-settings/preview","routing-settings","pzm-zones/preview","pzm-zones","pzm-zones/operations/11111111-1111-4111-8111-111111111111")){
|
|
mvc.perform(post("/api/administration/extras/"+path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/extras/"+path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void savedToursRequireEditRightsInAdditionToExtras() throws Exception {
|
|
for(String rights:List.of("0".repeat(46)+"1","1".repeat(70))){
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("existing-user"),eq("existing-password"),eq("existing-password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","existing-user","emp_rights",rights)));
|
|
var session=authenticated("existing-user","existing-password");var token=csrf(session);
|
|
for(String path:List.of("templates/preview","templates","customer-settings/preview","customer-settings","portal-settings/preview","portal-settings","lead-time/preview","lead-time","routing-settings/preview","routing-settings","pzm-zones/preview","pzm-zones")){
|
|
mvc.perform(post("/api/administration/extras/"+path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/extras/"+path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().is(rights.startsWith("0")?403:400));
|
|
}
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void zoneTariffsAndManagementRequireSessionCsrfAndAdministrationRight() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("existing-user"),eq("existing-password"),eq("existing-password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","existing-user","emp_rights","1")));
|
|
mvc.perform(get("/api/administration/zone-prices")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String path:List.of("/api/administration/zone-prices","/api/administration/zone-prices/management"))
|
|
mvc.perform(put(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
var token=csrf(session);
|
|
mvc.perform(put("/api/administration/zone-prices/management").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void tariffMutationsRequireSessionCsrfAndGeneralPriceRight() throws Exception {
|
|
mvc.perform(get("/api/administration/service-prices")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(put("/api/administration/service-prices").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(put("/api/administration/service-prices/definitions").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
var token=csrf(session);
|
|
mvc.perform(post("/api/administration/service-prices/query").session(session).header(token.header(),token.token()).contentType("application/json").content("{\"mode\":0,\"customer\":0,\"date\":\"2026-10-13\"}")).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void filterAdministrationRequiresSessionCsrfAndManageRight() throws Exception {
|
|
mvc.perform(get("/api/administration/filters")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/administration/filters").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.manage").value(false));
|
|
mvc.perform(post("/api/administration/filters").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
var token=csrf(session);
|
|
mvc.perform(post("/api/administration/filters").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(delete("/api/administration/filters/1").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/filters/operations/00000000-0000-0000-0000-000000000000/resume").session(session).header(token.header(),token.token())).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void addressAdministrationRequiresSessionCsrfAndHeadquartersRight() throws Exception {
|
|
mvc.perform(get("/api/administration/addresses/POSTAL")).andExpect(status().isUnauthorized());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("existing-user"),eq("existing-password"),eq("existing-password")))
|
|
.thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","existing-user","emp_rights","1")));
|
|
var session=authenticated("existing-user","existing-password");var token=csrf(session);
|
|
mvc.perform(get("/api/administration/addresses/POSTAL").session(session)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/administration/addresses/POSTAL","/api/administration/addresses/AREA/remove","/api/administration/addresses/AREA/query","/api/administration/addresses/AREA/operations/00000000-0000-0000-0000-000000000000/resume")) {
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
mvc.perform(post(path).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void customerDocumentsRequireSessionCsrfAndRights() throws Exception {
|
|
String base="/api/master/customers/9/service-documents";
|
|
for(String path:List.of(base,base+"/protocol/0",base+"/reasons/0",base+"/prices/20",base+"/prices/20/history"))mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String path:List.of(base+"/protocol/0",base+"/reasons/0",base+"/prices/20"))mvc.perform(put(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("none"),eq("password"),eq("password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","none","emp_rights","0000000000")));
|
|
var restricted=authenticated("none","password");var token=csrf(restricted);
|
|
for(String path:List.of(base+"/protocol/0",base+"/reasons/0",base+"/prices/20"))mvc.perform(put(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).customerDocumentWriteTransaction(any());
|
|
}
|
|
|
|
@Test void customerServiceImportsRequireSessionCsrfAndRights() throws Exception {
|
|
String base="/api/master/customers/9/service-imports";
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String path:List.of(base,base+"/preview"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("none"),eq("password"),eq("password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","none","emp_rights","0000000000")));
|
|
var restricted=authenticated("none","password");var token=csrf(restricted);
|
|
mvc.perform(get(base).session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(post(base+"/preview").session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void reportCustomerFinanceRequiresSessionAndBothReportAndCustomerRights() throws Exception {
|
|
String path="/api/administration/reports/CUSTOMER/customers/1/finance";
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
for(int only:List.of(0,16,5)){
|
|
char[] rights="0".repeat(70).toCharArray();rights[only]='1';String account="finance-"+only;
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq(account),eq("password"),eq("password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account",account,"emp_rights",new String(rights))));
|
|
mvc.perform(get(path).session(authenticated(account,"password"))).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).queryForList(startsWith("SELECT COALESCE(SUM(CASE"),any(Object[].class));
|
|
}
|
|
|
|
@Test void masterFormsRequireSessionCsrfAndObjectRights() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("none"),eq("password"),eq("password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","none","emp_rights","0000000000")));
|
|
var restricted=authenticated("none","password");var token=csrf(restricted);
|
|
for(String kind:List.of("customers","couriers","vehicles")){
|
|
String base="/api/master/"+kind+"/1/forms";
|
|
for(String path:List.of(base,base+"/3",base+"/3/history")){mvc.perform(get(path)).andExpect(status().isUnauthorized());mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());}
|
|
mvc.perform(put(base+"/3").session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
mvc.perform(put(base+"/3").session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).masterFormWriteTransaction(any());
|
|
}
|
|
|
|
@Test void customerCourierRelationsRequireSessionCsrfAndCourierRight() throws Exception {
|
|
String base="/api/master/relations";
|
|
for(String path:List.of(base+"/customers/1",base+"/couriers/1",base+"/search?kind=customers",base+"/history?customer=1&courier=1"))mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("none"),eq("password"),eq("password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","none","emp_rights","0000000000")));
|
|
var restricted=authenticated("none","password");var token=csrf(restricted);
|
|
for(String path:List.of(base+"/customers/1",base+"/couriers/1",base+"/search?kind=customers",base+"/history?customer=1&courier=1"))mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());
|
|
for(String path:List.of(base,base+"/preview",base+"/operations/00000000-0000-0000-0000-000000000000")){
|
|
mvc.perform(post(path).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).customerRelationWriteTransaction(any());
|
|
}
|
|
|
|
@Test void warehouseAdministrationRequiresItsOwnRightAndCsrf() throws Exception {
|
|
var restricted=authenticated("existing-user","existing-password");var token=csrf(restricted);
|
|
mvc.perform(get("/api/administration/warehouses")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/administration/warehouses").session(restricted)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/administration/warehouses/preview","/api/administration/warehouses/save","/api/administration/warehouse-inventory/query","/api/administration/warehouse-inventory/csv")){
|
|
mvc.perform(post(path).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
mvc.perform(get("/api/administration/warehouse-journal/options?stock=10")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/administration/warehouse-journal/options?stock=10").session(restricted)).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/administration/warehouse-journal/query","/api/administration/warehouse-journal/csv")){
|
|
mvc.perform(post(path).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
mvc.perform(put("/api/administration/warehouse-journal/10/columns").session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(put("/api/administration/warehouse-journal/10/columns").session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/administration/warehouse-movements/options?stock=10","/api/administration/warehouse-movements/articles?stock=10","/api/administration/warehouse-movements/serial?stock=10&article=1&serial=A","/api/administration/warehouse-movements/bundle?stock=10&article=1&quantity=1&to=10","/api/administration/warehouse-movements/journal/1?stock=10&mode=RETURN")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());
|
|
}
|
|
for(String path:List.of("/api/administration/warehouse-movements/preview","/api/administration/warehouse-movements/save")){
|
|
mvc.perform(post(path).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).warehouseMovementTransaction(any());
|
|
for(String path:List.of("/api/administration/warehouse-details/11","/api/administration/warehouse-details/11/cost-centers?headquarters=7","/api/administration/warehouse-details/11/address-suggestions?q=Test&country=DE&sessionToken=x","/api/administration/warehouse-details/11/address-details?placeId=x&sessionToken=x")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());
|
|
}
|
|
for(String path:List.of("/api/administration/warehouse-details/preview","/api/administration/warehouse-details/save")){
|
|
mvc.perform(post(path).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden());mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
for(String path:List.of("/api/administration/warehouse-barcodes?stock=10","/api/administration/warehouse-barcodes?stock=10&kind=COMMANDS")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());
|
|
}
|
|
for(String path:List.of("/api/administration/warehouse-scanner/resolve?stock=10&mode=OUT&code=ABC","/api/administration/warehouse-scanner/jobs?stock=10")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).warehouseDetailsTransaction(any());
|
|
verify(database,never()).warehouseWriteTransaction(any());
|
|
}
|
|
|
|
@Test void articleAdministrationRequiresArticleRightAndCsrf() throws Exception {
|
|
var restricted=authenticated("existing-user","existing-password");var token=csrf(restricted);
|
|
for(String path:List.of("/api/administration/articles","/api/administration/articles/options","/api/administration/articles/1","/api/administration/articles/1/history","/api/administration/article-groups")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());
|
|
}
|
|
for(String path:List.of("/api/administration/articles/preview","/api/administration/articles/save","/api/administration/article-groups")){
|
|
mvc.perform(post(path).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
for(String path:List.of("/api/account/article-columns","/api/administration/employees/1/article-columns")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
mvc.perform(put(path).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).articleWriteTransaction(any());
|
|
}
|
|
|
|
@Test void vehicleDispositionRequiresItsOwnRightAndCsrf() throws Exception {
|
|
String base="/api/administration/vehicle-disposition";
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(base+"/timelines?vehicles=101&day=2026-10-15")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(base+"/planning-data?job=10&day=2026-10-18")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(base+"/order-actions?vehicle=101&job=10&day=2026-10-18")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(base+"/single-handover?vehicle=101&job=10&day=2026-10-18")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(base+"/reservation-release?vehicle=101&job=10&day=2026-10-18")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(base+"/job-search?q=10")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(base+"/multiday/unreserved?day=2026-10-18")).andExpect(status().isUnauthorized());
|
|
var restricted=authenticated("existing-user","existing-password");var token=csrf(restricted);
|
|
mvc.perform(get(base).session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/1?day=2026-10-15").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/timelines?vehicles=101&day=2026-10-15").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/planning-data?job=10&day=2026-10-18").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/order-actions?vehicle=101&job=10&day=2026-10-18").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/single-handover?vehicle=101&job=10&day=2026-10-18").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/reservation-release?vehicle=101&job=10&day=2026-10-18").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/job-search?q=10").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/multiday/unreserved?day=2026-10-18").session(restricted)).andExpect(status().isForbidden());
|
|
for(String path:List.of(base,base+"/preview",base+"/customer-day",base+"/customer-day/preview",base+"/preferences",base+"/planning",base+"/planning/preview",base+"/multiday",base+"/multiday/preview",base+"/planning-data",base+"/planning-data/preview",base+"/order-actions",base+"/order-actions/preview",base+"/handover",base+"/handover/preview",base+"/single-handover",base+"/single-handover/preview",base+"/reservation-release",base+"/reservation-release/preview")){
|
|
mvc.perform(post(path).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
mvc.perform(get(base+"/customer-day?customer=1&day=2026-10-15").session(restricted)).andExpect(status().isForbidden());
|
|
verify(database,never()).vehicleCustomerDayWriteTransaction(anyBoolean(),any());
|
|
mvc.perform(get(base+"/planning?vehicle=101&day=2026-10-15").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/multiday?vehicle=101&day=2026-10-15&job=10").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/handover?vehicle=101&day=2026-10-15").session(restricted)).andExpect(status().isForbidden());
|
|
verify(database,never()).vehiclePlanningWriteTransaction(anyBoolean(),any());
|
|
verify(database,never()).vehicleOrderWriteTransaction(anyBoolean(),any());
|
|
mvc.perform(get(base+"/headquarters")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(base+"/headquarters").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"?headquarters=8").session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"/preferences")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get(base+"/preferences").session(restricted)).andExpect(status().isForbidden());
|
|
verify(database,never()).vehiclePreferencesWriteTransaction(anyBoolean(),any());
|
|
verify(database,never()).vehicleDispositionWriteTransaction(anyBoolean(),any());
|
|
}
|
|
|
|
@Test void rollcardsRequireSessionOrderReadAndDispositionScope() throws Exception {
|
|
String base="/api/administration/vehicle-disposition/101/rollcard?day=2026-10-15";
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());
|
|
var restricted=authenticated("existing-user","existing-password");
|
|
mvc.perform(get(base).session(restricted)).andExpect(status().isForbidden());
|
|
var rights=AppointmentServiceTest.user(27).rights();
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("rollcard-user"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","rollcard-user","emp_rights",rights)));
|
|
var session=authenticated("rollcard-user","pw");
|
|
mvc.perform(get(base).session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"&headquarters=8").session(session)).andExpect(status().isForbidden());
|
|
}
|
|
|
|
|
|
@Test void customerEntrySettingsRequireBothRightsAndCsrf() throws Exception {
|
|
String base="/api/master/customers/1/entry-settings";
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());
|
|
for(int omitted:List.of(0,2)){
|
|
String account="entry-settings-no-"+omitted;
|
|
var rights=new StringBuilder("1".repeat(70));rights.setCharAt(omitted,'0');
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq(account),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account",account,"emp_rights",rights.toString())));
|
|
var session=authenticated(account,"pw");var token=csrf(session);
|
|
mvc.perform(get(base).session(session)).andExpect(status().isForbidden());
|
|
for(String endpoint:List.of(base,base+"/preview")){
|
|
mvc.perform(post(endpoint).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
}
|
|
verify(database,never()).customerMailSettingsWriteTransaction(any());
|
|
}
|
|
|
|
@Test void customerReferenceLookupsRequireCustomerRight() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("reference-denied"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","reference-denied","emp_rights","0".repeat(70))));
|
|
var session=authenticated("reference-denied","pw");
|
|
for(String path:List.of("/api/master-data/customer-references/payer","/api/master-data/customer-references/parent/1")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());mvc.perform(get(path).session(session)).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void customerPreviousNumberRequiresCustomerRightAndCsrf() throws Exception {
|
|
String base="/api/master/customers/1/previous-number";
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());
|
|
for(int omitted:List.of(0)){
|
|
String account="previous-number-no-"+omitted;
|
|
var rights=new StringBuilder("1".repeat(70));rights.setCharAt(omitted,'0');
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq(account),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account",account,"emp_rights",rights.toString())));
|
|
var session=authenticated(account,"pw");var token=csrf(session);
|
|
mvc.perform(get(base).session(session)).andExpect(status().isForbidden());
|
|
for(String endpoint:List.of(base,base+"/preview")){
|
|
mvc.perform(post(endpoint).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void customerMailSettingsRequireBothRightsAndCsrf() throws Exception {
|
|
String base="/api/master/customers/1/mail-settings";
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());
|
|
for(int omitted:List.of(0,2)){
|
|
String account="mail-settings-no-"+omitted;
|
|
var rights=new StringBuilder("1".repeat(70));rights.setCharAt(omitted,'0');
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq(account),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account",account,"emp_rights",rights.toString())));
|
|
var session=authenticated(account,"pw");var token=csrf(session);
|
|
mvc.perform(get(base).session(session)).andExpect(status().isForbidden());
|
|
for(String endpoint:List.of(base,base+"/preview")){
|
|
mvc.perform(post(endpoint).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
}
|
|
verify(database,never()).customerMailSettingsWriteTransaction(any());
|
|
}
|
|
|
|
@Test void customerRollcardMailSettingsRequireBothRightsAndCsrf() throws Exception {
|
|
String base="/api/master/customers/1/rollcard-mail-settings";
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());
|
|
for(int omitted:List.of(0,2)){
|
|
String account="mail-settings-no-"+omitted;
|
|
var rights=new StringBuilder("1".repeat(70));rights.setCharAt(omitted,'0');
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq(account),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account",account,"emp_rights",rights.toString())));
|
|
var session=authenticated(account,"pw");var token=csrf(session);
|
|
mvc.perform(get(base).session(session)).andExpect(status().isForbidden());
|
|
for(String endpoint:List.of(base,base+"/preview")){
|
|
mvc.perform(post(endpoint).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
}
|
|
verify(database,never()).customerParameterWriteTransaction(any());
|
|
}
|
|
|
|
@Test void rollcardMailRequiresSessionRightsAndCsrf() throws Exception {
|
|
String base="/api/administration/vehicle-disposition/101/rollcard";
|
|
var restricted=authenticated("existing-user","existing-password");var token=csrf(restricted);
|
|
for(String endpoint:List.of(base+"/pdf?day=2026-10-15&revision=test",base+"/mail?day=2026-10-15",base+"/mail/00000000-0000-0000-0000-000000000001",base+"/mail/00000000-0000-0000-0000-000000000001/page?page=0")){
|
|
mvc.perform(get(endpoint)).andExpect(status().isUnauthorized());mvc.perform(get(endpoint).session(restricted)).andExpect(status().isForbidden());
|
|
}
|
|
for(String endpoint:List.of(base+"/mail/preview",base+"/mail/00000000-0000-0000-0000-000000000001")){
|
|
mvc.perform(post(endpoint).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(endpoint).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).vehicleRollcardMailWriteTransaction(anyBoolean(),any());
|
|
}
|
|
|
|
@Test void dispositionScopeCannotBeSelectedWithoutMasterRightOrAlterSession() throws Exception {
|
|
var rights=AppointmentServiceTest.user(27).rights();
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("fds-user"),any(),any())).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","fds-user","emp_rights",rights)));
|
|
var session=authenticated("fds-user","pw");var token=csrf(session);String base="/api/administration/vehicle-disposition";
|
|
for(String path:List.of(base+"?headquarters=8",base+"/103?day=2026-10-16&headquarters=8",base+"/customer-day?customer=3&day=2026-10-16&headquarters=8"))mvc.perform(get(path).session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(post(base+"/preview?headquarters=8").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(base+"/customer-day/preview?headquarters=8").session(session).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get(base+"?headquarters=wrong").session(session)).andExpect(status().isBadRequest());
|
|
var context=(SecurityContext)session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
|
assertThat(((LegacyUser)context.getAuthentication().getPrincipal()).headquartersId()).isEqualTo(7);
|
|
verify(database,never()).vehicleDispositionWriteTransaction(anyBoolean(),any());verify(database,never()).vehicleCustomerDayWriteTransaction(anyBoolean(),any());
|
|
}
|
|
|
|
@Test void vehicleRelationsRequireSessionCsrfAndCourierRight() throws Exception {
|
|
String base="/api/master/vehicle-relations";
|
|
for(String path:List.of(base+"/1",base+"/1/customers/2"))mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("none"),eq("password"),eq("password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","none","emp_rights","0000000000")));
|
|
var restricted=authenticated("none","password");var token=csrf(restricted);
|
|
for(String path:List.of(base+"/1",base+"/1/customers/2"))mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());
|
|
for(String path:List.of(base,base+"/preview")){
|
|
mvc.perform(post(path).session(restricted).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).vehicleRelationWriteTransaction(any());
|
|
}
|
|
|
|
@Test void customerServiceSettingsRequireSessionAndCsrf() throws Exception {
|
|
String base="/api/master/customers/9/service-settings";
|
|
mvc.perform(get(base)).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String path:List.of(base,base+"/preview"))
|
|
mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void portalEmployeesRequireSessionCsrfAndCustomerPlusEmployeeRights() throws Exception {
|
|
String base="/api/master-data/customers/1/portal-employees";var session=authenticated("existing-user","existing-password");
|
|
for(String path:List.of(base,base+"/2"))mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
for(String path:List.of(base,base+"/2/delete",base+"/2/two-factor/reset"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
for(String path:List.of(base+"/2",base+"/2/password"))mvc.perform(put(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String bits:List.of("1000","0001")){
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("portal-partial"),eq("password"),eq("password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","portal-partial","emp_rights",bits)));
|
|
var restricted=authenticated("portal-partial","password");var token=csrf(restricted);
|
|
for(String path:List.of(base,base+"/2"))mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());
|
|
for(String path:List.of(base,base+"/2/delete",base+"/2/two-factor/reset"))mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String path:List.of(base+"/2",base+"/2/password"))mvc.perform(put(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).portalEmployeeWriteTransaction(anyBoolean(),any());
|
|
}
|
|
|
|
@Test void portalAccountsRequireSessionCsrfAndDomainRights() throws Exception {
|
|
var session=authenticated("existing-user","existing-password");
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("no-portal"),eq("password"),eq("password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","no-portal","emp_rights","0".repeat(70))));
|
|
var restricted=authenticated("no-portal","password");var token=csrf(restricted);
|
|
for(String kind:List.of("customers","couriers")){
|
|
String path="/api/master-data/"+kind+"/1/access";
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
mvc.perform(put(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
mvc.perform(post(path+"/two-factor/reset").session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(get(path).session(restricted)).andExpect(status().isForbidden());
|
|
mvc.perform(put(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post(path+"/two-factor/reset").session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).portalAccountsWriteTransaction(any());
|
|
}
|
|
|
|
@Test void businessWorkflowsRequireSessionCsrfAndDomainRights() throws Exception {
|
|
for(String path:List.of("/api/master-data/customers/similar?phrase=Test","/api/master-data/customers/identifier","/api/master-data/couriers/identifier","/api/master-data/customers/1","/api/orders/1/workbench","/api/orders/1/cancellation","/api/orders/1/articles","/api/orders/1/calculation","/api/billing/options"))mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
for(String path:List.of("/api/orders/1/workbench","/api/orders/1/price","/api/master-data/customers/1"))mvc.perform(put(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
for(String path:List.of("/api/orders/1/actions","/api/orders/1/cancellation","/api/orders/1/cancellation/preview","/api/orders/1/articles/movement","/api/orders/1/articles/movement/preview","/api/billing/assign","/api/billing/operations/test/resume","/api/billing/invoice-documents/preview","/api/billing/invoice-documents/document.pdf","/api/billing/customer-csv/preview","/api/billing/customer-csv/download","/api/billing/customer-csv/store"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("none"),eq("password"),eq("password"))).thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","none","emp_rights","0000000000")));
|
|
var restricted=authenticated("none","password");var token=csrf(restricted);
|
|
mvc.perform(put("/api/master-data/customers/1").session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/orders/1/actions").session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/billing/assign").session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
for(String path:List.of("/api/billing/invoice-documents/preview","/api/billing/invoice-documents/document.pdf","/api/billing/customer-csv/preview","/api/billing/customer-csv/download","/api/billing/customer-csv/store")){
|
|
mvc.perform(get(path)).andExpect(status().isUnauthorized());
|
|
mvc.perform(post(path).session(restricted).header(token.header(),token.token()).contentType("application/json").content("{}")).andExpect(status().isForbidden());
|
|
}
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void healthAndSystemArePublicButDataRequiresSession() throws Exception {
|
|
mvc.perform(get("/api/health").with(httpBasic("proxy-fixture","proxy-password")))
|
|
.andExpect(status().isOk()).andExpect(jsonPath("$.status").value("UP"));
|
|
mvc.perform(get("/api/system")).andExpect(status().isOk()).andExpect(jsonPath("$.readOnly").value(true))
|
|
.andExpect(jsonPath("$.schemaChanges").value(false)).andExpect(jsonPath("$.capabilities.writeOrders").value(false));
|
|
MvcResult unauthorized = mvc.perform(get("/api/orders")).andExpect(status().isUnauthorized())
|
|
.andExpect(header().doesNotExist("WWW-Authenticate")).andReturn();
|
|
assertThat(unauthorized.getRequest().getSession(false)).isNull();
|
|
mvc.perform(get("/api/orders/42/history")).andExpect(status().isUnauthorized())
|
|
.andExpect(header().doesNotExist("WWW-Authenticate"));
|
|
Csrf csrf = csrf(null);
|
|
mvc.perform(post("/api/orders").session(csrf.session()).header(csrf.header(),csrf.token())
|
|
.contentType("application/json").content("{}"))
|
|
.andExpect(status().isUnauthorized());
|
|
}
|
|
|
|
@Test void reportsRequireSessionCsrfAndTheirSeparateLegacyRight() throws Exception {
|
|
mvc.perform(get("/api/administration/reports/CUSTOMER/options")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/administration").session(session)).andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.customerReports").value(false)).andExpect(jsonPath("$.courierReports").value(false));
|
|
mvc.perform(get("/api/administration/reports/CUSTOMER/options").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/administration/reports/CUSTOMER").session(session).contentType("application/json").content("{}"))
|
|
.andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
var token=csrf(session);
|
|
mvc.perform(post("/api/administration/reports/CUSTOMER").session(session).header(token.header(),token.token()).contentType("application/json").content("{}"))
|
|
.andExpect(status().isForbidden());
|
|
verify(database,never()).insert(anyString(),anyMap());
|
|
}
|
|
|
|
@Test void existingLoginPersistsSessionRotatesIdAndStoresNoPassword() throws Exception {
|
|
Csrf anonymous = csrf(null);
|
|
String previousId = anonymous.session().getId();
|
|
MvcResult login = login(anonymous,"existing-user","existing-password",200);
|
|
var body = json.readTree(login.getResponse().getContentAsString());
|
|
assertThat(body.get("username").asText()).isEqualTo("existing-user");
|
|
assertThat(body.has("password")).isFalse();
|
|
MockHttpSession session = (MockHttpSession) login.getRequest().getSession(false);
|
|
assertThat(session.getId()).isNotEqualTo(previousId);
|
|
SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
|
assertThat(context.getAuthentication().getCredentials()).isNull();
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.headquartersId").value(7)).andExpect(jsonPath("$.roles[0]").value("VIEWER"))
|
|
.andExpect(jsonPath("$.password").doesNotExist()).andExpect(jsonPath("$.capabilities.exportOrders").value(true))
|
|
.andExpect(jsonPath("$.capabilities.writeOrders").value(true));
|
|
mvc.perform(get("/api/me").session(session).with(httpBasic("proxy-fixture","proxy-password")))
|
|
.andExpect(status().isOk()).andExpect(jsonPath("$.username").value("existing-user"));
|
|
verify(database,times(1)).queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),any(),any(),any());
|
|
}
|
|
|
|
@Test void basicCredentialsAloneCannotAuthenticateTheApplication() throws Exception {
|
|
mvc.perform(get("/api/me").with(httpBasic("existing-user","existing-password")))
|
|
.andExpect(status().isUnauthorized()).andExpect(header().doesNotExist("WWW-Authenticate"));
|
|
verify(database,never()).queryForList(anyString(),any(),any(),any());
|
|
}
|
|
|
|
@Test void loginRequiresCsrfAndDoesNotAcceptCredentialsInGetQuery() throws Exception {
|
|
mvc.perform(post("/api/login").param("username","existing-user").param("password","existing-password"))
|
|
.andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
mvc.perform(get("/api/login").param("username","existing-user").param("password","existing-password"))
|
|
.andExpect(status().isUnauthorized());
|
|
verify(database,never()).queryForList(anyString(),any(),any(),any());
|
|
}
|
|
|
|
@Test void loginRotatesCsrfAndLogoutInvalidatesSession() throws Exception {
|
|
Csrf anonymous = csrf(null);
|
|
MockHttpSession session = (MockHttpSession) login(anonymous,"existing-user","existing-password",200).getRequest().getSession(false);
|
|
mvc.perform(post("/api/logout").session(session).header(anonymous.header(),anonymous.token()))
|
|
.andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk());
|
|
Csrf authenticated = csrf(session);
|
|
mvc.perform(post("/api/logout").session(session).header(authenticated.header(),authenticated.token()))
|
|
.andExpect(status().isNoContent()).andExpect(cookie().maxAge("VOTIANSESSION",0));
|
|
assertThat(session.isInvalid()).isTrue();
|
|
mvc.perform(get("/api/me")).andExpect(status().isUnauthorized());
|
|
Csrf afterLogout = csrf(null);
|
|
mvc.perform(post("/api/login").session(afterLogout.session()).header(authenticated.header(),authenticated.token())
|
|
.param("username","existing-user").param("password","existing-password"))
|
|
.andExpect(status().isForbidden());
|
|
login(afterLogout,"existing-user","existing-password",200);
|
|
}
|
|
|
|
@Test void logoutRequiresCsrfAndGetCannotLogOut() throws Exception {
|
|
MockHttpSession session = authenticated("existing-user","existing-password");
|
|
mvc.perform(post("/api/logout").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/logout").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk());
|
|
}
|
|
|
|
@Test void csrfCannotBeReusedAcrossSessions() throws Exception {
|
|
Csrf first = csrf(null);
|
|
Csrf second = csrf(null);
|
|
mvc.perform(post("/api/login").session(second.session()).header(first.header(),first.token())
|
|
.param("username","existing-user").param("password","existing-password"))
|
|
.andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
verify(database,never()).queryForList(anyString(),any(),any(),any());
|
|
}
|
|
|
|
@Test void unsupportedMutationsRemainDisabled() throws Exception {
|
|
MockHttpSession session = authenticated("existing-user","existing-password");
|
|
Csrf csrf = csrf(session);
|
|
mvc.perform(post("/api/orders/12/cancel").session(session).header(csrf.header(),csrf.token())
|
|
.contentType("application/json").content("{\"version\":1,\"cancelMode\":\"FREE\",\"reason\":\"test\"}"))
|
|
.andExpect(status().isNotImplemented());
|
|
verify(database,never()).update(anyString(),any(Object[].class));
|
|
}
|
|
|
|
@Test void orderEntryHasItsOwnRightAndStillRequiresCsrf() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("entry"),eq("password"),eq("password")))
|
|
.thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","entry","emp_rights","0000000100")));
|
|
var session=authenticated("entry","password");
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.capabilities.createOrders").value(true)).andExpect(jsonPath("$.capabilities.readCustomers").value(false))
|
|
.andExpect(jsonPath("$.capabilities.writeOrders").value(false));
|
|
mvc.perform(get("/api/orders/entry/options").session(session)).andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.entryModes.copyAccounting").value(false)).andExpect(jsonPath("$.entryModes.laterAccounting").value(false));
|
|
mvc.perform(get("/api/orders/entry/cost-centers").session(session)).andExpect(status().isOk());
|
|
mvc.perform(get("/api/orders/entry/vehicles").session(session)).andExpect(status().isOk());
|
|
mvc.perform(get("/api/orders/entry/courier-parents?vehicleType=2").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/customers").session(session)).andExpect(status().isForbidden());
|
|
for(String endpoint:List.of("/api/orders","/api/orders/quote","/api/orders/entry/optimize","/api/orders/entry/postal-quote"))
|
|
mvc.perform(post(endpoint).session(session).contentType("application/json").content(json.writeValueAsString(OrderEntryFixtures.request())))
|
|
.andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void orderEntryRejectsUnknownServerOwnedFieldsAndInvalidCommands() throws Exception {
|
|
var csrf=csrf(authenticated("existing-user","existing-password"));
|
|
var input=json.valueToTree(OrderEntryFixtures.request());
|
|
((com.fasterxml.jackson.databind.node.ObjectNode)input).put("hq_id",999);
|
|
mvc.perform(post("/api/orders").session(csrf.session()).header(csrf.header(),csrf.token())
|
|
.contentType("application/json").content(json.writeValueAsString(input))).andExpect(status().isBadRequest());
|
|
mvc.perform(post("/api/orders").session(csrf.session()).header(csrf.header(),csrf.token())
|
|
.contentType("application/json").content("{}")).andExpect(status().isBadRequest());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void orderEntryIsForbiddenWithoutRightSeven() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("viewer"),eq("password"),eq("password")))
|
|
.thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","viewer","emp_rights","1111111011")));
|
|
var csrf=csrf(authenticated("viewer","password"));
|
|
mvc.perform(get("/api/orders/entry/options").session(csrf.session())).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/entry/courier-parents?vehicleType=2").session(csrf.session())).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/entry/saved-orders/88?dispatch=true").session(csrf.session())).andExpect(status().isForbidden());
|
|
for(String endpoint:List.of("/api/orders","/api/orders/quote"))
|
|
mvc.perform(post(endpoint).session(csrf.session()).header(csrf.header(),csrf.token())
|
|
.contentType("application/json").content(json.writeValueAsString(OrderEntryFixtures.request())))
|
|
.andExpect(status().isForbidden());
|
|
String token=java.util.UUID.randomUUID().toString();
|
|
mvc.perform(get("/api/orders/entry/address-suggestions").param("q","Teststraße").param("sessionToken",token).session(csrf.session())).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/entry/address-details").param("placeId","test-id").param("sessionToken",token).session(csrf.session())).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/orders/entry/postal-quote").session(csrf.session()).header(csrf.header(),csrf.token()).contentType("application/json").content("{\"stops\":[{\"postalCode\":\"20095\"},{\"postalCode\":\"20097\"}],\"vehicleTypeId\":2}")).andExpect(status().isForbidden());
|
|
var stops=new java.util.ArrayList<>(OrderEntryFixtures.request().stops());stops.addAll(OrderEntryFixtures.request().stops());
|
|
mvc.perform(post("/api/orders/entry/optimize").session(csrf.session()).header(csrf.header(),csrf.token()).contentType("application/json").content(json.writeValueAsString(new OrderEntryModels.OptimizeInput(stops,true)))).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
@Test void backendDatabaseFailureIs503NotFakeLoginOrEmptyDataset() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("offline"),eq("password"),eq("password")))
|
|
.thenThrow(new DataAccessResourceFailureException("server secret must not be exposed"));
|
|
var result = login(csrf(null),"offline","password",503);
|
|
assertThat(json.readTree(result.getResponse().getContentAsString()).get("message").asText()).isEqualTo(ApiErrors.DATABASE_UNAVAILABLE);
|
|
assertThat(result.getRequest().getSession(false).getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)).isNull();
|
|
when(database.checkConnection()).thenReturn(new DatabaseService.ConnectionCheck(false,"UNAVAILABLE",ApiErrors.DATABASE_UNAVAILABLE));
|
|
mvc.perform(get("/api/system")).andExpect(status().isOk()).andExpect(jsonPath("$.connected").value(false));
|
|
}
|
|
|
|
@Test void wrongOrUnscopedAccountDoesNotAuthenticate() throws Exception {
|
|
Csrf wrong = csrf(null);
|
|
login(wrong,"unknown","incorrect",401);
|
|
mvc.perform(get("/api/me").session(wrong.session())).andExpect(status().isUnauthorized());
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("invalid"),eq("password"),eq("password")))
|
|
.thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",0,"usr_account","invalid","emp_rights","111111")));
|
|
login(csrf(null),"invalid","password",401);
|
|
}
|
|
|
|
@Test void rightsAreEnforcedBeyondAuthentication() throws Exception {
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("restricted"),eq("password"),eq("password")))
|
|
.thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","restricted","emp_rights","0000000000")));
|
|
MockHttpSession session = authenticated("restricted","password");
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.capabilities.exportOrders").value(false));
|
|
mvc.perform(get("/api/orders").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/customers").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/export.csv").session(session)).andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/orders/42/history").session(session)).andExpect(status().isForbidden());
|
|
verify(database,never()).queryForList(contains("phoenix_log"),any(Object[].class));
|
|
}
|
|
|
|
@Test void historyOfMissingOrForeignOrderReturns404WithoutReadingLog() throws Exception {
|
|
MockHttpSession session = authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/orders/42/history").session(session)).andExpect(status().isNotFound());
|
|
verify(database).queryForList("SELECT jb.jb_id FROM job jb WHERE jb.jb_id=? AND jb.hq_id=?",42L,7L);
|
|
verify(database,never()).queryForList(contains("phoenix_log"),any(Object[].class));
|
|
}
|
|
|
|
@Test void accessibleOrderWithNoLogReturnsEmptyHistory() throws Exception {
|
|
MockHttpSession session = authenticated("existing-user","existing-password");
|
|
when(database.queryForList("SELECT jb.jb_id FROM job jb WHERE jb.jb_id=? AND jb.hq_id=?",42L,7L))
|
|
.thenReturn(List.of(Map.of("jb_id",42L)));
|
|
|
|
mvc.perform(get("/api/orders/42/history").session(session))
|
|
.andExpect(status().isOk()).andExpect(content().json("[]"));
|
|
verify(database).queryForList(contains("FROM `phoenix_log`.log l"),eq(42L),eq(7L));
|
|
}
|
|
|
|
@Test void historyReturnsExistingEvents() throws Exception {
|
|
MockHttpSession session = authenticated("existing-user","existing-password");
|
|
when(database.queryForList("SELECT jb.jb_id FROM job jb WHERE jb.jb_id=? AND jb.hq_id=?",42L,7L))
|
|
.thenReturn(List.of(Map.of("jb_id",42L)));
|
|
when(database.queryForList(contains("FROM `phoenix_log`.log l"),eq(42L),eq(7L)))
|
|
.thenReturn(List.of(Map.of("log_id",81L,"logo_id",1,"log_createtime","2026-10-07 08:15:00",
|
|
"logo_op_name","Auftrag angelegt","logo_description","Testhistorie","usr_account","disponent")));
|
|
|
|
mvc.perform(get("/api/orders/42/history").session(session)).andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.length()").value(1)).andExpect(jsonPath("$[0].id").value(81))
|
|
.andExpect(jsonPath("$[0].at").value("2026-10-07T08:15:00"))
|
|
.andExpect(jsonPath("$[0].action").isString()).andExpect(jsonPath("$[0].actor").isString());
|
|
}
|
|
|
|
@Test void logFailureReturns503AndDoesNotDiscloseDatabaseDetails() throws Exception {
|
|
MockHttpSession session = authenticated("existing-user","existing-password");
|
|
when(database.queryForList("SELECT jb.jb_id FROM job jb WHERE jb.jb_id=? AND jb.hq_id=?",42L,7L))
|
|
.thenReturn(List.of(Map.of("jb_id",42L)));
|
|
when(database.queryForList(contains("FROM `phoenix_log`.log l"),eq(42L),eq(7L)))
|
|
.thenThrow(new DataAccessResourceFailureException("private database credentials"));
|
|
|
|
var response=mvc.perform(get("/api/orders/42/history").session(session))
|
|
.andExpect(status().isServiceUnavailable()).andReturn();
|
|
String message=json.readTree(response.getResponse().getContentAsString()).get("message").asText();
|
|
assertThat(message).contains("Auftragshistorie").doesNotContain("private database credentials");
|
|
}
|
|
|
|
@Test void databaseAccountDenialIsDistinguishedFromNetworkFailure() throws Exception {
|
|
when(database.checkConnection()).thenReturn(new DatabaseService.ConnectionCheck(false,"AUTHENTICATION_FAILED","Bitte Hostfreigabe prüfen."));
|
|
mvc.perform(get("/api/system")).andExpect(status().isOk()).andExpect(jsonPath("$.connected").value(false))
|
|
.andExpect(jsonPath("$.reason").value("AUTHENTICATION_FAILED"))
|
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("Hostfreigabe")));
|
|
}
|
|
@Test void inventoryAndMapEndpointsRequireEntryRightsAndStockRights() throws Exception {
|
|
mvc.perform(get("/api/orders/entry/articles")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/orders/entry/features")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/orders/entry/features").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.bookStock").value(false));
|
|
mvc.perform(get("/api/orders/entry/stocks").session(session).param("payerId","41").param("articleId","3")).andExpect(status().isForbidden());
|
|
mvc.perform(post("/api/orders/entry/map/address").session(session).contentType("application/json").content("{\"latitude\":53,\"longitude\":9}")).andExpect(status().isForbidden());
|
|
var csrf=csrf(session);
|
|
mvc.perform(post("/api/orders/entry/map/address").session(session).header(csrf.header(),csrf.token()).contentType("application/json").content("{\"latitude\":91,\"longitude\":9}")).andExpect(status().isBadRequest());
|
|
}
|
|
|
|
@Test void administrationAndStatisticsRequireSessionRightsAndCsrf() throws Exception {
|
|
mvc.perform(get("/api/administration")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/administration/holidays/2026")).andExpect(status().isUnauthorized());
|
|
mvc.perform(get("/api/statistics/options")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/administration").session(session)).andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.holidays").value(true)).andExpect(jsonPath("$.groups").value(false));
|
|
mvc.perform(put("/api/administration/holidays/2026").session(session)
|
|
.contentType("application/json").content("{}"))
|
|
.andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
var token=csrf(session);
|
|
mvc.perform(post("/api/administration/groups").session(session).header(token.header(),token.token())
|
|
.contentType("application/json").content("{\"name\":\"x\",\"shared\":false}"))
|
|
.andExpect(status().isForbidden());
|
|
mvc.perform(get("/api/administration/travel-times").session(session).param("mode","unknown").param("from","1").param("to","2"))
|
|
.andExpect(status().isBadRequest());
|
|
verify(database,never()).update(anyString(),any(Object[].class));
|
|
}
|
|
|
|
@Test void appointmentsRequireSessionSalesRightsAndCsrf() throws Exception {
|
|
mvc.perform(get("/api/appointments/options")).andExpect(status().isUnauthorized());
|
|
var session=authenticated("existing-user","existing-password");
|
|
mvc.perform(get("/api/me").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.capabilities.readAppointments").value(true));
|
|
for(String endpoint:List.of("/api/appointments","/api/appointments/query","/api/appointments/44/confirm","/api/appointments/44/finish","/api/appointments/44/resubmit"))
|
|
mvc.perform(post(endpoint).session(session).contentType("application/json").content("{}"))
|
|
.andExpect(status().isForbidden()).andExpect(jsonPath("$.code").value("CSRF_INVALID"));
|
|
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("sales-disabled"),eq("password"),eq("password")))
|
|
.thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","sales-disabled","emp_rights","100000000000")));
|
|
var restricted=authenticated("sales-disabled","password");
|
|
mvc.perform(get("/api/appointments/options").session(restricted)).andExpect(status().isForbidden());
|
|
verify(database,never()).writeTransaction(any());
|
|
}
|
|
|
|
}
|