From 8824a547bc4a2c7338b24f29f6ac290fd1ed02ba Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Tue, 30 Jan 2024 20:04:41 +0100 Subject: [PATCH] feat: add keycloak role configuration --- .../ffsaf/domain/service/KeycloakService.java | 16 ++ .../ffsaf/rest/CompteEndpoints.java | 30 +++ .../ffsaf/rest/from/MemberPermForm.java | 21 ++ src/main/webapp/src/components/ClubSelect.jsx | 6 +- src/main/webapp/src/hooks/useLoading.jsx | 2 +- src/main/webapp/src/pages/admin/AdminRoot.jsx | 6 +- .../webapp/src/pages/admin/MemberPage.jsx | 219 +++++++++++------- 7 files changed, 213 insertions(+), 87 deletions(-) create mode 100644 src/main/java/fr/titionfire/ffsaf/rest/from/MemberPermForm.java diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java index 8c0ddcd..6574fa5 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java @@ -13,6 +13,7 @@ import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RoleScopeResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.RoleRepresentation; @@ -94,6 +95,21 @@ public class KeycloakService { }); } + public Uni> fetchRole(String id) { + return vertx.getOrCreateContext().executeBlocking(() -> + keycloak.realm(realm).users().get(id).roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).toList()); + } + + public Uni updateRole(String id, List toAdd, List toRemove) { + return vertx.getOrCreateContext().executeBlocking(() -> { + RoleScopeResource resource = keycloak.realm(realm).users().get(id).roles().realmLevel(); + List roles = keycloak.realm(realm) .roles().list(); + resource.add(roles.stream().filter(r -> toAdd.contains(r.getName())).toList()); + resource.remove(roles.stream().filter(r -> toRemove.contains(r.getName())).toList()); + return "OK"; + }); + } + public Uni initCompte(long id) { return membreService.getById(id).invoke(Unchecked.consumer(membreModel -> { if (membreModel.getUserId() != null) diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java index 01c3440..299759b 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java @@ -1,6 +1,7 @@ package fr.titionfire.ffsaf.rest; import fr.titionfire.ffsaf.domain.service.KeycloakService; +import fr.titionfire.ffsaf.rest.from.MemberPermForm; import io.smallrye.mutiny.Uni; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; @@ -9,6 +10,9 @@ import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import java.util.ArrayList; +import java.util.List; + @Path("api/compte") public class CompteEndpoints { @@ -28,4 +32,30 @@ public class CompteEndpoints { public Uni initCompte(@PathParam("id") long id) { return service.initCompte(id); } + + @GET + @Path("{id}/roles") + @RolesAllowed("federation_admin") + public Uni getRole(@PathParam("id") String id) { + return service.fetchRole(id); + } + + @PUT + @Path("{id}/roles") + @RolesAllowed("federation_admin") + public Uni updateRole(@PathParam("id") String id, MemberPermForm form) { + List toAdd = new ArrayList<>(); + List toRemove = new ArrayList<>(); + + if (form.isFederation_admin()) toAdd.add("federation_admin"); + else toRemove.add("federation_admin"); + if (form.isSafca_super_admin()) toAdd.add("safca_super_admin"); + else toRemove.add("safca_super_admin"); + if (form.isSafca_user()) toAdd.add("safca_user"); + else toRemove.add("safca_user"); + if (form.isSafca_create_compet()) toAdd.add("safca_create_compet"); + else toRemove.add("safca_create_compet"); + + return service.updateRole(id, toAdd, toRemove); + } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/from/MemberPermForm.java b/src/main/java/fr/titionfire/ffsaf/rest/from/MemberPermForm.java new file mode 100644 index 0000000..a99832d --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/from/MemberPermForm.java @@ -0,0 +1,21 @@ +package fr.titionfire.ffsaf.rest.from; + +import jakarta.ws.rs.FormParam; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class MemberPermForm { + @FormParam("federation_admin") + private boolean federation_admin; + + @FormParam("safca_user") + private boolean safca_user; + + @FormParam("safca_create_compet") + private boolean safca_create_compet; + + @FormParam("safca_super_admin") + private boolean safca_super_admin; +} diff --git a/src/main/webapp/src/components/ClubSelect.jsx b/src/main/webapp/src/components/ClubSelect.jsx index 667639e..9b7d906 100644 --- a/src/main/webapp/src/components/ClubSelect.jsx +++ b/src/main/webapp/src/components/ClubSelect.jsx @@ -1,13 +1,13 @@ -import {LoadingContextProvider, useLoadingSwitcher} from "../hooks/useLoading.jsx"; +import {LoadingProvider, useLoadingSwitcher} from "../hooks/useLoading.jsx"; import {useFetch} from "../hooks/useFetch.js"; import {AxiosError} from "./AxiosError.jsx"; export function ClubSelect({defaultValue, name}) { - return + return
-
+ } function ClubSelect_({defaultValue, name}) { diff --git a/src/main/webapp/src/hooks/useLoading.jsx b/src/main/webapp/src/hooks/useLoading.jsx index c1bce06..f438387 100644 --- a/src/main/webapp/src/hooks/useLoading.jsx +++ b/src/main/webapp/src/hooks/useLoading.jsx @@ -13,7 +13,7 @@ export function useLoadingSwitcher() { return useContext(LoadingSwitcherContext); } -export function LoadingContextProvider({children}) { +export function LoadingProvider({children}) { const [showOverlay, setOverlay] = useState(0); return diff --git a/src/main/webapp/src/pages/admin/AdminRoot.jsx b/src/main/webapp/src/pages/admin/AdminRoot.jsx index 397fa27..472c38f 100644 --- a/src/main/webapp/src/pages/admin/AdminRoot.jsx +++ b/src/main/webapp/src/pages/admin/AdminRoot.jsx @@ -1,15 +1,15 @@ import {NavLink, Outlet} from "react-router-dom"; import './AdminRoot.css' -import {LoadingContextProvider} from "../../hooks/useLoading.jsx"; +import {LoadingProvider} from "../../hooks/useLoading.jsx"; import {MemberList} from "./MemberList.jsx"; import {MemberPage} from "./MemberPage.jsx"; export function AdminRoot() { return <>

Espace administration

- + - + } diff --git a/src/main/webapp/src/pages/admin/MemberPage.jsx b/src/main/webapp/src/pages/admin/MemberPage.jsx index 3373412..bcc3c75 100644 --- a/src/main/webapp/src/pages/admin/MemberPage.jsx +++ b/src/main/webapp/src/pages/admin/MemberPage.jsx @@ -1,5 +1,5 @@ import {useNavigate, useParams} from "react-router-dom"; -import {useLoadingSwitcher} from "../../hooks/useLoading.jsx"; +import {LoadingProvider, useLoadingSwitcher} from "../../hooks/useLoading.jsx"; import {useFetch, useFetchPut} from "../../hooks/useFetch.js"; import {AxiosError} from "../../components/AxiosError.jsx"; import {ClubSelect} from "../../components/ClubSelect.jsx"; @@ -18,63 +18,6 @@ export function MemberPage() { const setLoading = useLoadingSwitcher() const {data, error} = useFetch(`/member/${id}`, setLoading, 1) - const handleSubmit = (event) => { - event.preventDefault(); - setLoading(1) - - const formData = new FormData(); - formData.append("id", data.id); - formData.append("lname", event.target.lname?.value); - formData.append("fname", event.target.fname?.value); - formData.append("categorie", event.target.category?.value); - formData.append("club", event.target.club?.value); - formData.append("genre", event.target.genre?.value); - formData.append("country", event.target.country?.value); - formData.append("birth_date", new Date(event.target.birth_date?.value).toUTCString()); - formData.append("email", event.target.email?.value); - formData.append("role", event.target.role?.value); - formData.append("grade_arbitrage", event.target.grade_arbitrage?.value); - - const send = (formData_) => { - apiAxios.post(`/member/${id}`, formData_, { - headers: { - 'Accept': '*/*', - 'Content-Type': 'multipart/form-data', - } - }).then(data => { - console.log(data.data) // TODO - }).catch(e => { - console.log(e.response) // TODO - }).finally(() => { - if (setLoading) - setLoading(0) - }) - } - - const imageFile = event.target.url_photo.files[0]; - if (imageFile) { - console.log(`originalFile size ${imageFile.size / 1024 / 1024} MB`); - - const options = { - maxSizeMB: 1, - maxWidthOrHeight: 1920, - useWebWorker: true, - } - - imageCompression(imageFile, options).then(compressedFile => { - console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB - formData.append("photo_data", compressedFile) - send(formData) - }); - } else { - send(formData) - } - } - - const handleSubmitPerm = (event) => { - - } - return <>

Page membre

+ {userData.userId && } - ; + +} + +function PremFormContent({userData}) { + const setLoading = useLoadingSwitcher() + const {data, error} = useFetch(`/compte/${userData.userId}/roles`, setLoading, 1) + + return <> +
+
FFSAF intra
+ {data + ? <> + + + : error && } +
+
+
SAFCA
+ {data + ? <> + + + + + : error && } +
+ } function LicenceCard() { @@ -214,12 +266,15 @@ function SelectCard() { function CompteInfo({userData}) { const creatAccount = () => { + let err = {}; toast.promise( - apiAxios.put(`/compte/${userData.id}/init`), + apiAxios.put(`/compte/${userData.id}/init`).catch(e => { + err = e + }), { pending: 'Création du compte en cours', success: 'Compte créé avec succès 🎉', - error: 'Échec de la création du compte 😕' + error: 'Échec de la création du compte 😕 (code: ' + err.response.status + ')' } ) } @@ -229,7 +284,8 @@ function CompteInfo({userData}) {
{userData.userId ? - : <> + : + <>
Ce membre ne dispose pas de compte...
@@ -240,13 +296,16 @@ function CompteInfo({userData}) {
- } + + }
} -function CompteInfoContent({userData}) { +function CompteInfoContent({ + userData + }) { const setLoading = useLoadingSwitcher() const {data, error} = useFetch(`/compte/${userData.userId}`, setLoading, 1)