From e290b9314c2cf06c81f4d66bb47ff7fd741c19d7 Mon Sep 17 00:00:00 2001 From: Arthur Wambst Date: Sat, 23 Aug 2025 06:25:55 +0200 Subject: [PATCH] feat: role operations --- .../backend/data/model/Instance.java | 1 - .../backend/data/model/RolesAsso.java | 20 +++++ .../la_banquise/backend/data/model/User.java | 34 +++++++- .../backend/rest/UserEndpoints.java | 87 ++++++++++++------- .../backend/rest/UsersEndpoints.java | 74 ++++++++++++++++ .../backend/services/ContainerService.java | 6 +- .../backend/services/InstanceService.java | 6 +- .../backend/services/UserService.java | 14 ++- src/main/resources/import-dev.sql | 3 +- 9 files changed, 201 insertions(+), 44 deletions(-) create mode 100644 src/main/java/fr/la_banquise/backend/data/model/RolesAsso.java create mode 100644 src/main/java/fr/la_banquise/backend/rest/UsersEndpoints.java diff --git a/src/main/java/fr/la_banquise/backend/data/model/Instance.java b/src/main/java/fr/la_banquise/backend/data/model/Instance.java index bc15fe5..437a62c 100644 --- a/src/main/java/fr/la_banquise/backend/data/model/Instance.java +++ b/src/main/java/fr/la_banquise/backend/data/model/Instance.java @@ -1,6 +1,5 @@ package fr.la_banquise.backend.data.model; -import com.fasterxml.jackson.annotation.JsonBackReference; import com.github.dockerjava.api.model.Container; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/src/main/java/fr/la_banquise/backend/data/model/RolesAsso.java b/src/main/java/fr/la_banquise/backend/data/model/RolesAsso.java new file mode 100644 index 0000000..ee4d0ac --- /dev/null +++ b/src/main/java/fr/la_banquise/backend/data/model/RolesAsso.java @@ -0,0 +1,20 @@ +package fr.la_banquise.backend.data.model; + +public enum RolesAsso { + ROOT("ROOT"), // ROOT should always be the first + MODO("MODO"), + PINGOUIN("PINGOUIN"), + JI("JI"), + NONE("NONE"); + + private final String roleName; + + RolesAsso(String roleName) { this.roleName = roleName; } + + public String getRoleName() { return roleName; } + + @Override + public String toString() { + return roleName; + } +} diff --git a/src/main/java/fr/la_banquise/backend/data/model/User.java b/src/main/java/fr/la_banquise/backend/data/model/User.java index 7cb82db..03af65a 100644 --- a/src/main/java/fr/la_banquise/backend/data/model/User.java +++ b/src/main/java/fr/la_banquise/backend/data/model/User.java @@ -1,12 +1,17 @@ package fr.la_banquise.backend.data.model; -import com.fasterxml.jackson.annotation.JsonManagedReference; import io.quarkus.security.jpa.Password; import io.quarkus.security.jpa.Roles; +import io.quarkus.security.jpa.RolesValue; import io.quarkus.security.jpa.UserDefinition; import io.quarkus.security.jpa.Username; import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -14,7 +19,11 @@ import jakarta.persistence.ManyToMany; import jakarta.persistence.OneToMany; import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -38,7 +47,11 @@ public class User { public Long id; @Username public String name; @Password public String password; - @Roles public String role; + + @Enumerated(EnumType.STRING) + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "user_roles") + public Set role; //@JsonManagedReference @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) @@ -50,11 +63,24 @@ public class User { @ManyToMany(mappedBy = "respos", cascade = CascadeType.ALL) public List jiRespo; - public User(String name, String password, String role, + // Méthode pour Quarkus Security - conversion simple + @RolesValue + @Roles + public Set getRoles() { + return role.stream() + .filter(r -> r != RolesAsso.NONE) + .map(Enum::name) + .collect(Collectors.toSet()); + } + + public User(String name, String password, RolesAsso role, List instances) { this.name = name; this.password = password; - this.role = role; + if (role == RolesAsso.NONE) + this.role = new HashSet<>(); + else + this.role = new HashSet<>(Arrays.asList(role)); this.instances = instances; } } diff --git a/src/main/java/fr/la_banquise/backend/rest/UserEndpoints.java b/src/main/java/fr/la_banquise/backend/rest/UserEndpoints.java index 2ca6405..d9c2d94 100644 --- a/src/main/java/fr/la_banquise/backend/rest/UserEndpoints.java +++ b/src/main/java/fr/la_banquise/backend/rest/UserEndpoints.java @@ -1,8 +1,12 @@ package fr.la_banquise.backend.rest; +import fr.la_banquise.backend.data.model.RolesAsso; +import fr.la_banquise.backend.data.model.User; import fr.la_banquise.backend.rest.request.BulkUserDelRequest; import fr.la_banquise.backend.rest.request.BulkUserPostRequest; import fr.la_banquise.backend.rest.request.UserRequest; +import fr.la_banquise.backend.rest.response.BulkUserDelResponse; +import fr.la_banquise.backend.rest.response.BulkUserPostResponse; import fr.la_banquise.backend.rest.response.LoggedUserResponse; import fr.la_banquise.backend.services.UserService; import io.quarkus.security.Authenticated; @@ -18,11 +22,13 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import java.util.HashMap; +import java.util.Map; /** * UserEndpoints */ -@Path("/api/users") +@Path("/api/user") @Produces(MediaType.APPLICATION_JSON) public class UserEndpoints { @@ -40,46 +46,67 @@ public class UserEndpoints { } @GET - @RolesAllowed("root") - public Response getAllUsers() { - return Response.ok(userService.getAllUsers()).build(); - } - - @GET - @RolesAllowed("root") + @RolesAllowed("ROOT") @Path("{id}") public Response getUser(@PathParam("id") Long id) { return Response.ok(userService.getUser(id)).build(); } + @GET + @RolesAllowed("ROOT") + @Path("{id}/roles") + public Response getRoles(@PathParam("id") Long userId) { + try { + User user = userService.getUser(userId); + return Response.ok(user.role).build(); + } catch (Exception e) { + return Response.status(404) + .entity(Map.of("User or Role not found", e)) + .build(); + } + } + @POST - @RolesAllowed("root") + @RolesAllowed("ROOT") + @Path("{id}/roles") + public Response addRole(@PathParam("id") Long userId, + @QueryParam("role") String role) { + try { + User user = userService.getUser(userId); + user.role.add(userService.fromString(role)); + return Response.ok(user.role).build(); + } catch (Exception e) { + return Response.status(404) + .entity(Map.of("User or Role not found", e)) + .build(); + } + } + + @DELETE + @RolesAllowed("ROOT") + @Path("{id}/roles") + public Response removeRole(@PathParam("id") Long userId, + @QueryParam("role") String role) { + try { + User user = userService.getUser(userId); + user.role.remove(userService.fromString(role)); + return Response.ok(user.role).build(); + } catch (Exception e) { + return Response.status(404) + .entity(Map.of("User or Role not found", e)) + .build(); + } + } + + + @POST + @RolesAllowed("ROOT") public Response createUser(UserRequest user) { return Response.ok(userService.createUser(user)).build(); } - @POST - @RolesAllowed("root") // TODO: respos JI doivent aussi pouvoir faire ca - @Path("/bulk") - // INFO: if response is empty => required associated jiId was not found in - // existing JIs - public Response createUsersBulk(BulkUserPostRequest users) { - userService.createUsers( - users); // TODO: adapter en fonction de la reponse - return Response.ok().build(); - } - @DELETE - @RolesAllowed("root") - @Path("/bulk") - public Response deleteUserBulk(BulkUserDelRequest users) { - userService.deleteUsers( - users); // TODO: adapter en focntion de la reponse - return Response.ok().build(); - } - - @DELETE - @RolesAllowed("root") + @RolesAllowed("ROOT") public Response deleteUser(@QueryParam("id") Long id) { userService.deleteUser(id); return Response.ok().build(); diff --git a/src/main/java/fr/la_banquise/backend/rest/UsersEndpoints.java b/src/main/java/fr/la_banquise/backend/rest/UsersEndpoints.java new file mode 100644 index 0000000..e6fa0f2 --- /dev/null +++ b/src/main/java/fr/la_banquise/backend/rest/UsersEndpoints.java @@ -0,0 +1,74 @@ +package fr.la_banquise.backend.rest; + +import fr.la_banquise.backend.data.model.RolesAsso; +import fr.la_banquise.backend.data.model.User; +import fr.la_banquise.backend.rest.request.BulkUserDelRequest; +import fr.la_banquise.backend.rest.request.BulkUserPostRequest; +import fr.la_banquise.backend.rest.request.UserRequest; +import fr.la_banquise.backend.rest.response.BulkUserDelResponse; +import fr.la_banquise.backend.rest.response.BulkUserPostResponse; +import fr.la_banquise.backend.rest.response.LoggedUserResponse; +import fr.la_banquise.backend.services.UserService; +import io.quarkus.security.Authenticated; +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.HashMap; +import java.util.Map; + +/** + * UserEndpoints + */ +@Path("/api/users") +@Produces(MediaType.APPLICATION_JSON) +public class UsersEndpoints { + + @Inject SecurityIdentity identity; + + @Inject UserService userService; + + @GET + @RolesAllowed("ROOT") + public Response getAllUsers() { + return Response.ok(userService.getAllUsers()).build(); + } + + @POST + @RolesAllowed("ROOT") // TODO: respos JI doivent aussi pouvoir faire ca + // INFO: if response is empty => required associated jiId was not found in + // existing JIs + public Response createUsersBulk(BulkUserPostRequest users) { + BulkUserPostResponse response = userService.createUsers( + users); + if (response.success_names.size() == users.users.size()) + return Response.ok().build(); + return Response.status(202) + .entity(Map.of("These users were already created : ", + response.already_created)) + .build(); + } + + @DELETE + @RolesAllowed("ROOT") + public Response deleteUserBulk(BulkUserDelRequest users) { + BulkUserDelResponse response = userService.deleteUsers(users); + if (response.success_names.size() == users.usernames.size()) + return Response.ok().build(); + + Map retour = new HashMap(); + for (int id = 0; id < response.failed_names.size(); id++) { + retour.put(response.failed_names.get(id), + response.failed_reasons.get(id)); + } + return Response.status(202).entity(retour).build(); + } +} diff --git a/src/main/java/fr/la_banquise/backend/services/ContainerService.java b/src/main/java/fr/la_banquise/backend/services/ContainerService.java index fe98b4c..3c2565d 100644 --- a/src/main/java/fr/la_banquise/backend/services/ContainerService.java +++ b/src/main/java/fr/la_banquise/backend/services/ContainerService.java @@ -1,15 +1,13 @@ -/**package fr.la_banquise.backend.services; +/*package fr.la_banquise.backend.services; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.command.InspectContainerResponse; -// import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.ContainerPort; import com.github.dockerjava.api.model.ExposedPort; import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.api.model.PortBinding; import com.github.dockerjava.api.model.Ports; -import fr.la_banquise.backend.data.model.Container; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.util.HashMap; @@ -47,7 +45,7 @@ public class ContainerService { return container.getId(); } - public Container createContainer() { + /*public Container createContainer() { Container container = dockerClient .createContainerCmd("nginx:latest") diff --git a/src/main/java/fr/la_banquise/backend/services/InstanceService.java b/src/main/java/fr/la_banquise/backend/services/InstanceService.java index 608ec15..7b8cf37 100644 --- a/src/main/java/fr/la_banquise/backend/services/InstanceService.java +++ b/src/main/java/fr/la_banquise/backend/services/InstanceService.java @@ -27,7 +27,7 @@ public class InstanceService { return instanceRepository.findAll().list(); } - public List getAllInstances(String username) { + public List getInstancesByOwner(String username) { User user = userRepository.findByName(username); return instanceRepository.find("owner", user).list(); } @@ -37,8 +37,8 @@ public class InstanceService { } @Transactional - public Instance createInstance(String name, String ssh, String pwd, - Long port, String username, Long sujetId) { + public Instance createInstance(String name, Long port, String username, + Long sujetId) { User user = userRepository.findByName(username); Sujet sujet = sujetRepository.findById(sujetId); Instance instance = new Instance(name, port, user, sujet); diff --git a/src/main/java/fr/la_banquise/backend/services/UserService.java b/src/main/java/fr/la_banquise/backend/services/UserService.java index f1aec56..5da50f1 100644 --- a/src/main/java/fr/la_banquise/backend/services/UserService.java +++ b/src/main/java/fr/la_banquise/backend/services/UserService.java @@ -1,5 +1,6 @@ package fr.la_banquise.backend.services; +import fr.la_banquise.backend.data.model.RolesAsso; import fr.la_banquise.backend.data.model.User; import fr.la_banquise.backend.data.repository.JiRepository; import fr.la_banquise.backend.data.repository.UserRepository; @@ -34,7 +35,7 @@ public class UserService { public User createUser(UserRequest request) { User user = new User(request.name, BcryptUtil.bcryptHash(request.password), - "pingouin", new ArrayList<>()); + RolesAsso.NONE, new ArrayList<>()); userRepository.persist(user); return user; } @@ -93,5 +94,16 @@ public class UserService { } return response; + } + + + public RolesAsso fromString(String role_str) { + return switch (role_str) { + case "ROOT" -> RolesAsso.ROOT; + case "MODO" -> RolesAsso.MODO; + case "PINGOUIN" -> RolesAsso.PINGOUIN; + case "JI" -> RolesAsso.JI; + default -> throw new Error("Wrong role str"); + }; } } diff --git a/src/main/resources/import-dev.sql b/src/main/resources/import-dev.sql index cbe1bb8..ace0c48 100644 --- a/src/main/resources/import-dev.sql +++ b/src/main/resources/import-dev.sql @@ -1,2 +1,3 @@ -- Ce fichier est exécuté automatiquement en mode dev -INSERT INTO penguin (name, password, role) VALUES ('root', '$2a$10$lzKAv4aj6s0jtneg0Ikx/eEBb6p.6N6yo7ZF.myqYxEA9MWbMwvNu', 'root'); +INSERT INTO penguin (name, password) VALUES ('root', '$2a$10$lzKAv4aj6s0jtneg0Ikx/eEBb6p.6N6yo7ZF.myqYxEA9MWbMwvNu'); +INSERT INTO user_roles (User_id, role) VALUES (1, 'ROOT');