From 6b38405e94a588428e5631c5751dc434418ca1f9 Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Thu, 15 Aug 2024 13:56:54 +0200 Subject: [PATCH] feat: secure match ans poule endpoints --- .../ffsaf/data/model/PouleModel.java | 2 +- .../domain/service/CompetPermService.java | 24 ++++++++-- .../ffsaf/domain/service/MatchService.java | 32 ++++++------- .../ffsaf/domain/service/PouleService.java | 45 +++++++++++++------ .../titionfire/ffsaf/rest/ClubEndpoints.java | 3 +- .../titionfire/ffsaf/rest/MatchEndpoints.java | 18 +++----- .../titionfire/ffsaf/rest/PouleEndpoints.java | 19 +++----- 7 files changed, 82 insertions(+), 61 deletions(-) diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java index e18590b..f48bafb 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java @@ -29,7 +29,7 @@ public class PouleModel { String name = ""; - @ManyToOne + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "id_compet", referencedColumnName = "id") CompetitionModel compet; diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java index 254a335..3064021 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java @@ -65,13 +65,21 @@ public class CompetPermService { }); } + public Uni hasViewPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) { + return hasViewPerm(securityCtx, Uni.createFrom().item(competitionModel)); + } + public Uni hasViewPerm(SecurityCtx securityCtx, long id) { - return competitionRepository.findById(id).call(o -> ( + return hasViewPerm(securityCtx, competitionRepository.findById(id)); + } + + private Uni hasViewPerm(SecurityCtx securityCtx, Uni in) { + return in.call(o -> ( securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin")) ? Uni.createFrom().nullItem() : o.getSystem() == CompetitionSystem.SAFCA ? - hasSafcaViewPerm(securityCtx, id) + hasSafcaViewPerm(securityCtx, o.getId()) : Uni.createFrom().nullItem().invoke(Unchecked.consumer(__ -> { if (!securityCtx.isInClubGroup(o.getClub().getId())) throw new DForbiddenException(); @@ -79,13 +87,21 @@ public class CompetPermService { )); } + public Uni hasEditPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) { + return hasEditPerm(securityCtx, Uni.createFrom().item(competitionModel)); + } + public Uni hasEditPerm(SecurityCtx securityCtx, long id) { - return competitionRepository.findById(id).call(o -> ( + return hasEditPerm(securityCtx, competitionRepository.findById(id)); + } + + public Uni hasEditPerm(SecurityCtx securityCtx, Uni in) { + return in.call(o -> ( securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin")) ? Uni.createFrom().nullItem() : o.getSystem() == CompetitionSystem.SAFCA ? - hasSafcaEditPerm(securityCtx, id) + hasSafcaEditPerm(securityCtx, o.getId()) : Uni.createFrom().nullItem().invoke(Unchecked.consumer(__ -> { if (!securityCtx.isInClubGroup(o.getClub().getId())) throw new DForbiddenException(); diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java index fb7b97f..71c6896 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java @@ -1,6 +1,5 @@ package fr.titionfire.ffsaf.domain.service; -import fr.titionfire.ffsaf.data.model.ClubModel; import fr.titionfire.ffsaf.data.model.MatchModel; import fr.titionfire.ffsaf.data.repository.CombRepository; import fr.titionfire.ffsaf.data.repository.MatchRepository; @@ -8,6 +7,7 @@ import fr.titionfire.ffsaf.data.repository.PouleRepository; import fr.titionfire.ffsaf.rest.data.MatchData; import fr.titionfire.ffsaf.utils.CompetitionSystem; import fr.titionfire.ffsaf.utils.ScoreEmbeddable; +import fr.titionfire.ffsaf.utils.SecurityCtx; import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.smallrye.mutiny.Uni; @@ -15,7 +15,6 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.util.List; -import java.util.function.Consumer; @WithSession @ApplicationScoped @@ -30,30 +29,32 @@ public class MatchService { @Inject CombRepository combRepository; - public Uni getById(Consumer checkPerm, CompetitionSystem system, Long id) { + @Inject + CompetPermService permService; + + public Uni getById(SecurityCtx securityCtx, CompetitionSystem system, Long id) { return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult() .onItem().ifNull().failWith(() -> new RuntimeException("Match not found")) - .call(data -> pouleRepository.findById(data.getPoule().getId()) - .invoke(data2 -> checkPerm.accept(data2.getCompet().getClub()))) + .call(data -> permService.hasViewPerm(securityCtx, data.getPoule().getCompet())) .map(MatchData::fromModel); } - public Uni> getAllByPoule(Consumer checkPerm, CompetitionSystem system, Long id) { + public Uni> getAllByPoule(SecurityCtx securityCtx, CompetitionSystem system, Long id) { return pouleRepository.find("systemId = ?1 AND system = ?2", id, system).firstResult() .onItem().ifNull().failWith(() -> new RuntimeException("Poule not found")) - .invoke(data -> checkPerm.accept(data.getCompet().getClub())) + .call(data -> permService.hasViewPerm(securityCtx, data.getCompet())) .chain(data -> repository.list("poule = ?1", data.getId()) .map(o -> o.stream().map(MatchData::fromModel).toList())); } - public Uni addOrUpdate(Consumer checkPerm, CompetitionSystem system, MatchData data) { + public Uni addOrUpdate(SecurityCtx securityCtx, CompetitionSystem system, MatchData data) { return repository.find("systemId = ?1 AND system = ?2", data.getId(), system).firstResult() .chain(o -> { if (o == null) { return pouleRepository.find("systemId = ?1 AND system = ?2", data.getPoule(), system) .firstResult() .onItem().ifNull().failWith(() -> new RuntimeException("Poule not found")) - .invoke(data2 -> checkPerm.accept(data2.getCompet().getClub())) + .call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet())) .map(pouleModel -> { MatchModel model = new MatchModel(); @@ -67,7 +68,7 @@ public class MatchService { return pouleRepository.find("systemId = ?1 AND system = ?2", data.getPoule(), system) .firstResult() .onItem().ifNull().failWith(() -> new RuntimeException("Poule not found")) - .invoke(data2 -> checkPerm.accept(data2.getCompet().getClub())) + .call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet())) .map(__ -> o); } } @@ -91,9 +92,11 @@ public class MatchService { .map(MatchData::fromModel); } - public Uni updateScore(CompetitionSystem system, Long id, List scores) { + public Uni updateScore(SecurityCtx securityCtx, CompetitionSystem system, Long id, + List scores) { return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult() .onItem().ifNull().failWith(() -> new RuntimeException("Match not found")) + .call(o2 -> permService.hasEditPerm(securityCtx, o2.getPoule().getCompet())) .invoke(data -> { data.getScores().clear(); data.getScores().addAll(scores); @@ -102,11 +105,10 @@ public class MatchService { .map(o -> "OK"); } - public Uni delete(Consumer checkPerm, CompetitionSystem system, Long id) { + public Uni delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) { return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult() .onItem().ifNull().failWith(() -> new RuntimeException("Match not found")) - .chain(data -> pouleRepository.findById(data.getPoule().getId()) - .invoke(data2 -> checkPerm.accept(data2.getCompet().getClub())) - .chain(data2 -> Panache.withTransaction(() -> repository.delete(data)))); + .call(o2 -> permService.hasEditPerm(securityCtx, o2.getPoule().getCompet())) + .chain(data -> Panache.withTransaction(() -> repository.delete(data))); } } diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java index 90585ae..82a72d4 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java @@ -1,6 +1,9 @@ package fr.titionfire.ffsaf.domain.service; -import fr.titionfire.ffsaf.data.model.*; +import fr.titionfire.ffsaf.data.model.MatchModel; +import fr.titionfire.ffsaf.data.model.MembreModel; +import fr.titionfire.ffsaf.data.model.PouleModel; +import fr.titionfire.ffsaf.data.model.TreeModel; import fr.titionfire.ffsaf.data.repository.*; import fr.titionfire.ffsaf.rest.data.PouleData; import fr.titionfire.ffsaf.rest.data.PouleFullData; @@ -18,7 +21,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Objects; -import java.util.function.Consumer; import java.util.stream.Stream; @WithSession @@ -40,29 +42,44 @@ public class PouleService { @Inject CombRepository combRepository; - public Uni getById(Consumer checkPerm, CompetitionSystem system, Long id) { + @Inject + CompetPermService permService; + + public Uni getById(SecurityCtx securityCtx, CompetitionSystem system, Long id) { return repository.find("systemId = ?1 AND system = ?2", id, system) .firstResult() .onItem().ifNull().failWith(() -> new RuntimeException("Poule not found")) - .invoke(data -> checkPerm.accept(data.getCompet().getClub())) + .call(data -> permService.hasViewPerm(securityCtx, data.getCompet())) .map(PouleData::fromModel); } public Uni> getAll(SecurityCtx securityCtx, CompetitionSystem system) { return repository.list("system = ?1", system) - .map(data -> data.stream() - .filter(p -> securityCtx.roleHas("federation_admin") || - securityCtx.isInClubGroup(p.getCompet().getClub().getId())) - .map(PouleData::fromModel).toList()); + .chain(o -> + permService.getAllHaveAccess(securityCtx.getSubject()) + .chain(map -> Uni.createFrom().item(o.stream() + .filter(p -> { + if (securityCtx.getSubject().equals(p.getCompet().getOwner())) + return true; + if (p.getSystem() == CompetitionSystem.SAFCA) { + if (map.containsKey(p.getCompet().getId())) + return map.get(p.getId()).equals("admin"); + return securityCtx.roleHas("federation_admin") + || securityCtx.roleHas("safca_super_admin"); + } + return securityCtx.roleHas("federation_admin"); + }) + .map(PouleData::fromModel).toList()) + )); } - public Uni addOrUpdate(Consumer checkPerm, CompetitionSystem system, PouleData data) { + public Uni addOrUpdate(SecurityCtx securityCtx, CompetitionSystem system, PouleData data) { return repository.find("systemId = ?1 AND system = ?2", data.getId(), system).firstResult() .chain(o -> { if (o == null) { return competRepository.findById(data.getCompet()) .onItem().ifNull().failWith(() -> new RuntimeException("Competition not found")) - .invoke(o2 -> checkPerm.accept(o2.getClub())) + .call(o2 -> permService.hasEditPerm(securityCtx, o2)) .chain(competitionModel -> { PouleModel model = new PouleModel(); @@ -130,13 +147,14 @@ public class PouleService { .chain(o -> Panache.withTransaction(() -> treeRepository.persist(o))); } - public Uni syncPoule(CompetitionSystem system, PouleFullData data) { - System.out.println(data); + public Uni syncPoule(SecurityCtx securityCtx, CompetitionSystem system, PouleFullData data) { return repository.find("systemId = ?1 AND system = ?2", data.getId(), system) .firstResult() + .onItem().ifNotNull().call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet())) .onItem().ifNull().switchTo( () -> competRepository.findById(data.getCompet()) .onItem().ifNull().failWith(() -> new RuntimeException("Compet not found")) + .call(o -> permService.hasEditPerm(securityCtx, o)) .map(o -> { PouleModel model = new PouleModel(); model.setId(null); @@ -240,9 +258,10 @@ public class PouleService { List toRmNode; } - public Uni delete(Consumer checkPerm, CompetitionSystem system, Long id) { + public Uni delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) { return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult() .onItem().ifNull().failWith(() -> new RuntimeException("Poule not found")) + .call(o -> permService.hasEditPerm(securityCtx, o.getCompet())) .call(o -> Mutiny.fetch(o.getMatchs())) .call(o -> Mutiny.fetch(o.getTree()) .call(o2 -> o2.isEmpty() ? Uni.createFrom().nullItem() : diff --git a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java index c9acaf9..e3e473c 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java @@ -274,7 +274,6 @@ public class ClubEndpoints { @GET @Path("{clubId}/logo") - @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @Operation(summary = "Renvoie le logo du club", description = "Renvoie le logo du club spécifié") @APIResponses(value = { @APIResponse(responseCode = "200", description = "Le logo du club"), @@ -284,7 +283,7 @@ public class ClubEndpoints { }) public Uni getLogo( @Parameter(description = "Identifiant long (clubId) de club") @PathParam("clubId") String clubId) { - return clubService.getByClubId(clubId).onItem().invoke(checkPerm).chain(Unchecked.function(clubModel -> { + return clubService.getByClubId(clubId).chain(Unchecked.function(clubModel -> { try { return Utils.getMediaFile((clubModel != null) ? clubModel.getId() : -1, media, "ppClub", Uni.createFrom().nullItem()); diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java index 370a624..097dac9 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java @@ -1,21 +1,17 @@ package fr.titionfire.ffsaf.rest; -import fr.titionfire.ffsaf.data.model.ClubModel; import fr.titionfire.ffsaf.domain.service.MatchService; import fr.titionfire.ffsaf.rest.data.MatchData; -import fr.titionfire.ffsaf.rest.exception.DForbiddenException; import fr.titionfire.ffsaf.utils.CompetitionSystem; import fr.titionfire.ffsaf.utils.ScoreEmbeddable; import fr.titionfire.ffsaf.utils.SecurityCtx; import io.quarkus.security.Authenticated; import io.smallrye.mutiny.Uni; -import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import java.util.List; -import java.util.function.Consumer; @Authenticated @Path("api/match/{system}/") @@ -30,42 +26,38 @@ public class MatchEndpoints { @Inject SecurityCtx securityCtx; - Consumer checkPerm = Unchecked.consumer(clubModel -> { - if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(clubModel.getId())) - throw new DForbiddenException(); - }); @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Uni getById(@PathParam("id") Long id) { - return service.getById(checkPerm, system, id); + return service.getById(securityCtx, system, id); } @GET @Path("getAllByPoule/{id}") @Produces(MediaType.APPLICATION_JSON) public Uni> getAllByPoule(@PathParam("id") Long id) { - return service.getAllByPoule(checkPerm, system, id); + return service.getAllByPoule(securityCtx, system, id); } @POST @Produces(MediaType.APPLICATION_JSON) public Uni addOrUpdate(MatchData data) { - return service.addOrUpdate(checkPerm, system, data); + return service.addOrUpdate(securityCtx, system, data); } @POST @Path("score/{id}") @Produces(MediaType.APPLICATION_JSON) public Uni updateScore(@PathParam("id") Long id, List scores) { - return service.updateScore(system, id, scores); + return service.updateScore(securityCtx, system, id, scores); } @DELETE @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Uni delete(@PathParam("id") Long id) { - return service.delete(checkPerm, system, id); + return service.delete(securityCtx, system, id); } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java index 88f908e..8bc9dc1 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java @@ -1,22 +1,19 @@ package fr.titionfire.ffsaf.rest; -import fr.titionfire.ffsaf.data.model.ClubModel; import fr.titionfire.ffsaf.domain.service.PouleService; import fr.titionfire.ffsaf.rest.data.PouleData; import fr.titionfire.ffsaf.rest.data.PouleFullData; -import fr.titionfire.ffsaf.rest.exception.DForbiddenException; import fr.titionfire.ffsaf.utils.CompetitionSystem; import fr.titionfire.ffsaf.utils.SecurityCtx; +import io.quarkus.security.Authenticated; import io.smallrye.mutiny.Uni; -import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import java.util.List; -import java.util.function.Consumer; -// @Authenticated +@Authenticated @Path("api/poule/{system}/") public class PouleEndpoints { @@ -29,16 +26,12 @@ public class PouleEndpoints { @Inject SecurityCtx securityCtx; - Consumer checkPerm = Unchecked.consumer(clubModel -> { - if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(clubModel.getId())) - throw new DForbiddenException(); - }); @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Uni getById(@PathParam("id") Long id) { - return service.getById(checkPerm, system, id); + return service.getById(securityCtx, system, id); } @GET @@ -51,20 +44,20 @@ public class PouleEndpoints { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Uni addOrUpdate(PouleData data) { - return service.addOrUpdate(checkPerm, system, data); + return service.addOrUpdate(securityCtx, system, data); } @POST @Path("sync") @Consumes(MediaType.APPLICATION_JSON) public Uni syncPoule(PouleFullData data) { - return service.syncPoule(system, data); + return service.syncPoule(securityCtx, system, data); } @DELETE @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Uni delete(@PathParam("id") Long id) { - return service.delete(checkPerm, system, id); + return service.delete(securityCtx, system, id); } }