Erweitere VoTiAn NG um Fachfunktionen und verbessere die Listenoberfläche

Ergänze Auftragsbearbeitung, Disposition, Stammdaten, Kunden- und
Transporteurportale, Lagerverwaltung, Abrechnung, Exporte und Statistik.
Füge Kontoeinstellungen, Mitteilungen, Automatikdienste und Betriebsstatus
sowie die zugehörigen Tests und PHP-Abgleichsdokumentation hinzu.

Vergrößere die persönlichen Auftragslisten auf einheitliche Höhen und
richte Statusanzeigen sowie die Ranking-Vorschau-Steuerelemente bündig aus.

Validierung: 44 Frontendtests und Produktionsbuild erfolgreich.
Backend funktional unverändert gegenüber dem vollständigen Lauf mit 1.624
erfolgreichen und drei übersprungenen Tests. Layout im Browser und lokale
Bereitstellung geprüft.
This commit is contained in:
Sven committed 2026-10-20 11:19:43 +02:00
1 parent 363bc27c26
commit 781a2b82dc
1165 files changed
+74986 -1028

No files matched your search

@@ -32,10 +32,569 @@ class ApiSecurityTest {
@Autowired MockMvc mvc;
@Autowired ObjectMapper json;
@MockitoBean DatabaseService database;
@MockitoBean LoginActivity loginActivity;
@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")))
@@ -86,6 +645,30 @@ class ApiSecurityTest {
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());
@@ -132,16 +715,350 @@ class ApiSecurityTest {
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/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());
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"))mvc.perform(post(path).session(session).contentType("application/json").content("{}")).andExpect(status().isForbidden());
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());
}
@@ -259,11 +1176,13 @@ class ApiSecurityTest {
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());
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"))
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());
@@ -285,10 +1204,18 @@ class ApiSecurityTest {
.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());
}