Skip to content

Commit 7da2c28

Browse files
authored
Merge branch 'develop' into swt2#2186
2 parents e57b2e4 + 29578a8 commit 7da2c28

15 files changed

Lines changed: 665 additions & 20 deletions

File tree

bogenliga/bogenliga-application/src/main/java/de/bogenliga/application/services/v1/download/DownloadService.java

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import de.bogenliga.application.business.wettkampf.api.types.WettkampfDO;
99
import de.bogenliga.application.business.wettkampf.api.WettkampfComponent;
1010
import de.bogenliga.application.springconfiguration.security.permissions.RequiresPermission;
11+
import de.bogenliga.application.springconfiguration.security.permissions.RequiresOnePermissions;
1112
import de.bogenliga.application.springconfiguration.security.types.UserPermission;
1213
import org.slf4j.Logger;
1314
import org.slf4j.LoggerFactory;
@@ -18,16 +19,19 @@
1819
import org.springframework.http.ResponseEntity;
1920
import org.springframework.web.bind.annotation.*;
2021
import de.bogenliga.application.business.bogenkontrollliste.api.BogenkontrolllisteComponent;
22+
import de.bogenliga.application.business.dsbmannschaft.api.DsbMannschaftComponent;
2123
import de.bogenliga.application.business.meldezettel.api.MeldezettelComponent;
2224
import de.bogenliga.application.business.schusszettel.api.SchusszettelComponent;
2325
import de.bogenliga.application.business.setzliste.api.SetzlisteComponent;
2426
import de.bogenliga.application.business.lizenz.api.LizenzComponent;
2527
import de.bogenliga.application.business.rueckennummern.api.RueckennummernComponent;
2628
import de.bogenliga.application.common.errorhandling.ErrorCode;
29+
import de.bogenliga.application.common.errorhandling.exception.BusinessException;
2730
import de.bogenliga.application.common.errorhandling.exception.TechnicalException;
2831
import de.bogenliga.application.common.service.ServiceFacade;
2932
import de.bogenliga.application.common.validation.Preconditions;
3033
import de.bogenliga.application.services.v1.setzliste.service.SetzlisteService;
34+
import de.bogenliga.application.springconfiguration.security.permissions.RequiresOnePermissionAspect;
3135

3236

3337
/**
@@ -65,6 +69,8 @@ public class DownloadService implements ServiceFacade {
6569
private final BogenkontrolllisteComponent bogenkontrolllisteComponent;
6670
private final RueckennummernComponent rueckennummernComponent;
6771
private final WettkampfComponent wettkampfComponent;
72+
private final DsbMannschaftComponent dsbMannschaftComponent;
73+
private final RequiresOnePermissionAspect requiresOnePermissionAspect;
6874

6975

7076
/**
@@ -76,16 +82,20 @@ public DownloadService(final SetzlisteComponent setzlisteComponent, final Lizenz
7682
final MeldezettelComponent meldezettelComponent,
7783
final BogenkontrolllisteComponent bogenkontrolllisteComponent,
7884
final RueckennummernComponent rueckennummernComponent,
79-
final WettkampfComponent wettkampfComponent) {
85+
final WettkampfComponent wettkampfComponent,
86+
final DsbMannschaftComponent dsbMannschaftComponent,
87+
final RequiresOnePermissionAspect requiresOnePermissionAspect) {
8088
this.lizenzComponent = lizenzComponent;
8189
this.setzlisteComponent = setzlisteComponent;
8290
this.schusszettelComponent = schusszettelComponent;
8391
this.meldezettelComponent = meldezettelComponent;
8492
this.bogenkontrolllisteComponent = bogenkontrolllisteComponent;
8593
this.rueckennummernComponent = rueckennummernComponent;
8694
this.wettkampfComponent = wettkampfComponent;
95+
this.dsbMannschaftComponent = dsbMannschaftComponent;
96+
this.requiresOnePermissionAspect = requiresOnePermissionAspect;
8797
}
88-
98+
8999
/**
90100
* returns the Setzliste as pdf file for client download
91101
* <p>
@@ -231,8 +241,8 @@ ResponseEntity<InputStreamResource> downloadbogenkontrolllistePdf(@RequestParam(
231241
*/
232242
@CrossOrigin(maxAge = 0)
233243
@GetMapping(
234-
path = "pdf/rueckennummern",
235-
produces = MediaType.APPLICATION_PDF_VALUE)
244+
path = "pdf/rueckennummern",
245+
produces = MediaType.APPLICATION_PDF_VALUE)
236246
@RequiresPermission(UserPermission.CAN_READ_DEFAULT)
237247
public
238248
ResponseEntity<InputStreamResource> downloadRueckennummernPdf(@RequestParam("mannschaftid") final long mannschaftid) {
@@ -291,11 +301,32 @@ private ResponseEntity<InputStreamResource> generateInputStream(byte[] fileBloB)
291301
@GetMapping(
292302
path = "pdf/schuetzenlizenz/{dsbMitgliedId}/{teamId}",
293303
produces = MediaType.APPLICATION_PDF_VALUE)
294-
@RequiresPermission(UserPermission.CAN_READ_WETTKAMPF)
304+
@RequiresOnePermissions(perm = {UserPermission.CAN_MODIFY_MY_VEREIN, UserPermission.CAN_MODIFY_MY_VERANSTALTUNG})
295305
public ResponseEntity<InputStreamResource> downloadLizenz(@PathVariable("dsbMitgliedId") final long dsbMitgliedID,
296-
@PathVariable("teamId") final long teamID) {
306+
@PathVariable("teamId") final long teamID) {
297307
LOG.debug("dsbMitgliedID: {}", dsbMitgliedID);
298308
LOG.debug("teamID: {}", teamID);
309+
310+
// Data-specific authorization check
311+
var mannschaft = dsbMannschaftComponent.findById(teamID);
312+
if (mannschaft == null) {
313+
throw new BusinessException(ErrorCode.NO_PERMISSION_ERROR,
314+
"Team not found");
315+
}
316+
317+
// Check if user has permission to modify their own club (Sportleiter)
318+
boolean isSportleiter = requiresOnePermissionAspect.hasSpecificPermissionSportleiter(
319+
UserPermission.CAN_MODIFY_MY_VEREIN, mannschaft.getVereinId());
320+
321+
// Check if user has permission to modify event (Ligaleiter)
322+
boolean isLigaleiter = requiresOnePermissionAspect.hasSpecificPermissionLigaLeiterID(
323+
UserPermission.CAN_MODIFY_MY_VERANSTALTUNG, mannschaft.getVeranstaltungId());
324+
325+
if (!isSportleiter && !isLigaleiter) {
326+
throw new BusinessException(ErrorCode.NO_PERMISSION_ERROR,
327+
String.format("User does not have permission to download license for team %d", teamID));
328+
}
329+
299330
final byte[] fileBloB = lizenzComponent.getLizenzPDFasByteArray(dsbMitgliedID, teamID);
300331

301332
return generateInputStream(fileBloB);
@@ -314,10 +345,29 @@ public ResponseEntity<InputStreamResource> downloadLizenz(@PathVariable("dsbMitg
314345
@GetMapping(
315346
path = "pdf/lizenzen",
316347
produces = MediaType.APPLICATION_PDF_VALUE)
317-
@RequiresPermission(UserPermission.CAN_READ_DEFAULT)
348+
@RequiresOnePermissions(perm = {UserPermission.CAN_MODIFY_MY_VEREIN, UserPermission.CAN_MODIFY_MY_VERANSTALTUNG})
318349
public
319350
ResponseEntity<InputStreamResource> downloadLizenzenPdf(@RequestParam("mannschaftid") final long mannschaftid) {
320351

352+
// Data-specific authorization check
353+
var mannschaft = dsbMannschaftComponent.findById(mannschaftid);
354+
if (mannschaft == null) {
355+
throw new BusinessException(ErrorCode.NO_PERMISSION_ERROR,
356+
"Team not found");
357+
}
358+
359+
// Check if user has permission to modify their own club (Sportleiter)
360+
boolean isSportleiter = requiresOnePermissionAspect.hasSpecificPermissionSportleiter(
361+
UserPermission.CAN_MODIFY_MY_VEREIN, mannschaft.getVereinId());
362+
363+
// Check if user has permission to modify event (Ligaleiter)
364+
boolean isLigaleiter = requiresOnePermissionAspect.hasSpecificPermissionLigaLeiterID(
365+
UserPermission.CAN_MODIFY_MY_VERANSTALTUNG, mannschaft.getVeranstaltungId());
366+
367+
if (!isSportleiter && !isLigaleiter) {
368+
throw new BusinessException(ErrorCode.NO_PERMISSION_ERROR,
369+
String.format("User does not have permission to download licenses for team %d", mannschaftid));
370+
}
321371

322372
final byte[] fileBloB = lizenzComponent.getMannschaftsLizenzenPDFasByteArray(mannschaftid);
323373

@@ -334,16 +384,16 @@ ResponseEntity<InputStreamResource> downloadLizenzenPdf(@RequestParam("mannschaf
334384
* <pre>{@code Request: GET /v1/download/pdf/Einzelstatistik/?werte=x}</pre>
335385
*
336386
* @return pdf as InputStreamRessource
337-
*/
387+
*/
338388
@CrossOrigin(maxAge = 0)
339389
@GetMapping(
340390
path = "pdf/Einzelstatistik",
341391
produces = MediaType.APPLICATION_PDF_VALUE)
342392
@RequiresPermission(UserPermission.CAN_READ_DEFAULT)
343393
public
344394
ResponseEntity<InputStreamResource> downloadEinzelstatistikPdf(@RequestParam("veranstaltungsid") final long veranstaltungsid,
345-
@RequestParam("manschaftsid") final long manschaftsid,
346-
@RequestParam("jahr") final int jahr)
395+
@RequestParam("manschaftsid") final long manschaftsid,
396+
@RequestParam("jahr") final int jahr)
347397
{
348398

349399
final byte[] fileBloB = wettkampfComponent.getPDFasByteArray("Einzelstatistik",veranstaltungsid,manschaftsid,jahr);

bogenliga/bogenliga-application/src/main/java/de/bogenliga/application/services/v1/kampfrichter/service/KampfrichterSessionService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public KampfrichterSessionService(KampfrichterSessionDAO sessionDAO,
6161
this.dsbMitgliedComponent = dsbMitgliedComponent;
6262
}
6363

64-
@RequiresOnePermissions(perm = {UserPermission.CAN_MODIFY_WETTKAMPF, UserPermission.CAN_MODIFY_MY_WETTKAMPF})
64+
@RequiresOnePermissions(perm = {UserPermission.CAN_MODIFY_WETTKAMPF, UserPermission.CAN_MODIFY_MY_WETTKAMPF, UserPermission.CAN_MODIFY_MY_ORT})
6565
@GetMapping(value = "/token", produces = MediaType.APPLICATION_JSON_VALUE)
6666
public ResponseEntity<Map<String, String>> getOrCreateToken(@RequestParam Long wettkampfid,
6767
final Principal principal) {
@@ -83,7 +83,7 @@ public ResponseEntity<Map<String, String>> getOrCreateToken(@RequestParam Long w
8383
return ResponseEntity.ok(Map.of("token", token));
8484
}
8585

86-
@RequiresOnePermissions(perm = {UserPermission.CAN_MODIFY_WETTKAMPF, UserPermission.CAN_MODIFY_MY_WETTKAMPF})
86+
@RequiresOnePermissions(perm = {UserPermission.CAN_MODIFY_WETTKAMPF, UserPermission.CAN_MODIFY_MY_WETTKAMPF, UserPermission.CAN_MODIFY_MY_ORT})
8787
@PostMapping(value = "/tokenize", produces = MediaType.APPLICATION_JSON_VALUE)
8888
public ResponseEntity<Map<String, String>> regenerateToken(@RequestParam Long wettkampfid,
8989
final Principal principal) {

bogenliga/bogenliga-application/src/main/java/de/bogenliga/application/services/v1/match/service/MatchService.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,14 @@
3333
import de.bogenliga.application.business.passe.api.PasseComponent;
3434
import de.bogenliga.application.business.passe.api.types.PasseDO;
3535
import de.bogenliga.application.business.passe.impl.business.PasseComponentImpl;
36+
import de.bogenliga.application.business.schusszettel.api.TabletSchusszettelComponent;
3637
import de.bogenliga.application.business.vereine.api.VereinComponent;
3738
import de.bogenliga.application.business.vereine.api.types.VereinDO;
3839
import de.bogenliga.application.business.wettkampf.api.WettkampfComponent;
3940
import de.bogenliga.application.business.wettkampftyp.api.WettkampfTypComponent;
4041
import de.bogenliga.application.business.wettkampftyp.api.types.WettkampfTypDO;
42+
import de.bogenliga.application.common.errorhandling.ErrorCode;
43+
import de.bogenliga.application.common.errorhandling.exception.BusinessException;
4144
import de.bogenliga.application.common.service.ServiceFacade;
4245
import de.bogenliga.application.common.service.UserProvider;
4346
import de.bogenliga.application.common.service.types.DataTransferObject;
@@ -120,6 +123,7 @@ public static Map<String, String> getPasseConditionErrors()
120123
private final VereinComponent vereinComponent;
121124
private final RequiresOnePermissionAspect requiresOnePermissionAspect;
122125
private final VeranstaltungComponent veranstaltungComponent;
126+
private final TabletSchusszettelComponent tabletSchusszettelComponent;
123127

124128
/**
125129
* Constructor with dependency injection
@@ -136,6 +140,7 @@ public MatchService(final MatchComponent matchComponent,
136140
final MannschaftsmitgliedComponent mannschaftsmitgliedComponent,
137141
final WettkampfTypComponent wettkampftypComponent,
138142
final VeranstaltungComponent veranstaltungComponent,
143+
final TabletSchusszettelComponent tabletSchusszettelComponent,
139144
RequiresOnePermissionAspect requiresOnePermissionAspect) {
140145
this.matchComponent = matchComponent;
141146
this.passeComponent = passeComponent;
@@ -146,6 +151,7 @@ public MatchService(final MatchComponent matchComponent,
146151
this.wettkampfTypComponent = wettkampftypComponent;
147152
this.requiresOnePermissionAspect = requiresOnePermissionAspect;
148153
this.veranstaltungComponent = veranstaltungComponent;
154+
this.tabletSchusszettelComponent = tabletSchusszettelComponent;
149155
}
150156

151157
/**
@@ -257,6 +263,55 @@ public List<MatchDTO> findMatchesByIds(@PathVariable("matchId1") Long matchId1,
257263
return matches;
258264
}
259265

266+
/**
267+
* Read-only variant of {@link #findMatchesByIds} for anonymous QR-scanning tablet clients.
268+
* <p>
269+
* Instead of a permission check, this validates the same session token used by the
270+
* public tablet-schusszettel endpoint, and additionally verifies that the requested
271+
* matches actually belong to the token's wettkampf, so a token cannot be used to
272+
* browse matches of other competitions.
273+
*
274+
* @param matchId1 das erste Match auf dem Schusszettel
275+
* @param matchId2 das zweite Match auf dem Schusszettel
276+
* @param token Zugriffs-Token der Tablet-Session
277+
* @param wettkampfid Wettkampf-ID der Tablet-Session
278+
* @param teamid Team-ID der Tablet-Session
279+
*
280+
* @return MatchDTOs zu den IDs (2 Stück)
281+
*/
282+
@GetMapping(value = "schusszettel/tablet/{matchId1}/{matchId2}",
283+
produces = MediaType.APPLICATION_JSON_VALUE)
284+
public List<MatchDTO> findMatchesByIdsForTablet(@PathVariable("matchId1") Long matchId1,
285+
@PathVariable("matchId2") Long matchId2,
286+
@RequestParam String token,
287+
@RequestParam Long wettkampfid,
288+
@RequestParam Long teamid) {
289+
if (!tabletSchusszettelComponent.isValidToken(wettkampfid, teamid, token)) {
290+
throw new BusinessException(ErrorCode.NO_PERMISSION_ERROR, "Invalid or expired token");
291+
}
292+
293+
this.checkMatchId(matchId1);
294+
this.checkMatchId(matchId2);
295+
296+
MatchDTO matchDTO1 = getMatchFromId(matchId1, true);
297+
MatchDTO matchDTO2 = getMatchFromId(matchId2, true);
298+
299+
if (!matchDTO1.getWettkampfId().equals(wettkampfid) || !matchDTO2.getWettkampfId().equals(wettkampfid)) {
300+
throw new BusinessException(ErrorCode.NO_PERMISSION_ERROR, "Matches do not belong to the given Wettkampf");
301+
}
302+
303+
checkPreconditions(matchDTO1, matchConditionErrors);
304+
checkPreconditions(matchDTO2, matchConditionErrors);
305+
306+
List<MatchDTO> matches = new ArrayList<>();
307+
matches.add(matchDTO1);
308+
matches.add(matchDTO2);
309+
this.log(matchDTO1, SERVICE_FIND_MATCHES_BY_IDS);
310+
this.log(matchDTO2, SERVICE_FIND_MATCHES_BY_IDS);
311+
312+
return matches;
313+
}
314+
260315

261316
/**
262317
* I return the match entries of the database with the given mannschaftId.

bogenliga/bogenliga-application/src/main/java/de/bogenliga/application/services/v1/schusszettel/service/TabletSchusszettelService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ public ResponseEntity<Map<String, String>> reTokenize(
158158
*/
159159
@RequiresOnePermissions(perm = {
160160
UserPermission.CAN_MODIFY_WETTKAMPF,
161-
UserPermission.CAN_MODIFY_MY_WETTKAMPF})
161+
UserPermission.CAN_MODIFY_MY_WETTKAMPF,
162+
UserPermission.CAN_MODIFY_MY_ORT})
162163
@GetMapping("/sessions")
163164
public ResponseEntity<TabletSessionInfoDTO> getTabletSessionInfo(@RequestParam Long wettkampfid) {
164165

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package de.bogenliga.application.services.v1.wettkampf.model;
2+
3+
import de.bogenliga.application.common.service.types.DataTransferObject;
4+
5+
public class AnzeigenMatchDTO implements DataTransferObject {
6+
private static final long serialVersionUID = 5036117832594594995L;
7+
8+
private int matchNr;
9+
private String verein1;
10+
private String verein2;
11+
private Integer[] schuesseVerein1;
12+
private Integer[] schuesseVerein2;
13+
private int totalVerein1;
14+
private int totalVerein2;
15+
private int satzpunkteVerein1;
16+
private int satzpunkteVerein2;
17+
18+
public AnzeigenMatchDTO() {
19+
this.matchNr = 0;
20+
this.verein1 = "";
21+
this.verein2 = "";
22+
this.schuesseVerein1 = null;
23+
this.schuesseVerein2 = null;
24+
this.totalVerein1 = 0;
25+
this.totalVerein2 = 0;
26+
this.satzpunkteVerein1 = 0;
27+
this.satzpunkteVerein2 = 0;
28+
}
29+
30+
public AnzeigenMatchDTO(int matchNr, String verein1, String verein2, Integer[] schuesseVerein1, Integer[] schuesseVerein2, int totalVerein1, int totalVerein2, int satzpunkteVerein1, int satzpunkteVerein2) {
31+
this.matchNr = matchNr;
32+
this.verein1 = verein1;
33+
this.verein2 = verein2;
34+
this.schuesseVerein1 = schuesseVerein1;
35+
this.schuesseVerein2 = schuesseVerein2;
36+
this.totalVerein1 = totalVerein1;
37+
this.totalVerein2 = totalVerein2;
38+
this.satzpunkteVerein1 = satzpunkteVerein1;
39+
this.satzpunkteVerein2 = satzpunkteVerein2;
40+
}
41+
42+
public int getMatchNr() {
43+
return matchNr;
44+
}
45+
46+
public void setMatchNr(int matchNr) {
47+
this.matchNr = matchNr;
48+
}
49+
50+
public String getVerein1() {
51+
return verein1;
52+
}
53+
54+
public void setVerein1(String verein1) {
55+
this.verein1 = verein1;
56+
}
57+
58+
public String getVerein2() {
59+
return verein2;
60+
}
61+
62+
public void setVerein2(String verein2) {
63+
this.verein2 = verein2;
64+
}
65+
66+
public Integer[] getSchuesseVerein1() {
67+
return schuesseVerein1;
68+
}
69+
70+
public void setSchuesseVerein1(Integer[] schuesseVerein1) {
71+
this.schuesseVerein1 = schuesseVerein1;
72+
}
73+
74+
public Integer[] getSchuesseVerein2() {
75+
return schuesseVerein2;
76+
}
77+
78+
public void setSchuesseVerein2(Integer[] schuesseVerein2) {
79+
this.schuesseVerein2 = schuesseVerein2;
80+
}
81+
82+
public int getTotalVerein1() {
83+
return totalVerein1;
84+
}
85+
86+
public void setTotalVerein1(int totalVerein1) {
87+
this.totalVerein1 = totalVerein1;
88+
}
89+
90+
public int getTotalVerein2() {
91+
return totalVerein2;
92+
}
93+
94+
public void setTotalVerein2(int totalVerein2) {
95+
this.totalVerein2 = totalVerein2;
96+
}
97+
98+
public int getSatzpunkteVerein1() {
99+
return satzpunkteVerein1;
100+
}
101+
102+
public void setSatzpunkteVerein1(int satzpunkteVerein1) {
103+
this.satzpunkteVerein1 = satzpunkteVerein1;
104+
}
105+
106+
public int getSatzpunkteVerein2() {
107+
return satzpunkteVerein2;
108+
}
109+
110+
public void setSatzpunkteVerein2(int satzpunkteVerein2) {
111+
this.satzpunkteVerein2 = satzpunkteVerein2;
112+
}
113+
114+
}

0 commit comments

Comments
 (0)