From 249d00177ce044306b461cefe17dadbeffa559f8 Mon Sep 17 00:00:00 2001 From: Pierre Tellier Date: Tue, 11 Feb 2025 10:00:11 +0100 Subject: [PATCH 1/6] feat: interraction between the backend and keycloak --- .../enseirb/myinpulse/api/GetUserInfo.java | 11 +-- .../exceptions/RoleNotFoudException.java | 7 ++ .../exceptions/UserNotFoundException.java | 7 ++ .../security/KeycloakJwtRolesConverter.java | 11 ++- .../enseirb/myinpulse/utils/KeycloakApi.java | 77 +++++++++++++++++++ front/MyINPulse-front/src/services/api.ts | 3 +- front/MyINPulse-front/src/views/test.vue | 73 ++++++++++++++++++ 7 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/exceptions/RoleNotFoudException.java create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/exceptions/UserNotFoundException.java create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/utils/KeycloakApi.java create mode 100644 front/MyINPulse-front/src/views/test.vue diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/api/GetUserInfo.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/api/GetUserInfo.java index 50262e1..3b4f04a 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/api/GetUserInfo.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/api/GetUserInfo.java @@ -2,13 +2,13 @@ package enseirb.myinpulse.api; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.*; +import javax.management.relation.RoleNotFoundException; import java.security.Principal; + + @SpringBootApplication @RestController public class GetUserInfo { @@ -22,7 +22,8 @@ public class GetUserInfo { @CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS}) @GetMapping("/random") - public boolean rand(){ + public boolean rand(@RequestHeader("Authorization") String token) throws RoleNotFoundException { + System.err.println(token); System.err.println("HELLO"); return Math.random() > 0.5; } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/exceptions/RoleNotFoudException.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/exceptions/RoleNotFoudException.java new file mode 100644 index 0000000..22e4c23 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/exceptions/RoleNotFoudException.java @@ -0,0 +1,7 @@ +package enseirb.myinpulse.exceptions; + +public class RoleNotFoudException extends RuntimeException { + public RoleNotFoudException(String message) { + super(message); + } +} diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/exceptions/UserNotFoundException.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/exceptions/UserNotFoundException.java new file mode 100644 index 0000000..983e766 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/exceptions/UserNotFoundException.java @@ -0,0 +1,7 @@ +package enseirb.myinpulse.exceptions; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java index fafbef5..08c5cab 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java @@ -1,3 +1,7 @@ +/* + * Source: https://github.com/ChristianHuff-DEV/secure-spring-rest-api-using-keycloak/blob/main/src/main/java/io/betweendata/RestApi/security/oauth2/KeycloakJwtRolesConverter.java + * edited by Pierre Tellier + */ package enseirb.myinpulse.security; import org.springframework.core.convert.converter.Converter; @@ -41,17 +45,16 @@ public class KeycloakJwtRolesConverter implements Converter TEMPORARNAME(Jwt jwt) { + public Collection tokenRolesExtractor(Jwt jwt) { // Collection that will hold the extracted roles Collection grantedAuthorities = new ArrayList<>(); diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/KeycloakApi.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/KeycloakApi.java new file mode 100644 index 0000000..e91e9e1 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/KeycloakApi.java @@ -0,0 +1,77 @@ +package enseirb.myinpulse.utils; + +import enseirb.myinpulse.exceptions.UserNotFoundException; +import org.springframework.web.client.RestClient; + +import javax.management.relation.RoleNotFoundException; + +import static org.springframework.http.MediaType.APPLICATION_JSON; + +public class KeycloakApi { + + static final String keycloakUrl = "http://localhost:7080"; + static final String realmName = "test"; + + /** + * Uses Keycloak API to retrieve a role representation of a role by its name + * @param roleName name of the role + * @param bearer authorization header used by the client to authenticate to keycloak + */ + static public RoleRepresentation getRoleRepresentationByName(String roleName, String bearer) throws RoleNotFoundException { + RoleRepresentation[] response = RestClient.builder().baseUrl(keycloakUrl) + .defaultHeader("Authorization", bearer) + .build() + .get() + .uri("/admin/realms/{realmName}/roles/{roleName}", realmName, roleName) + .retrieve() + .body(RoleRepresentation[].class); + + if (response == null || response.length == 0) { + throw new RoleNotFoundException("Role not found"); + } + return response[0]; + } + + static public String getUserIdByName(String username, String bearer) throws UserNotFoundException { + UserRepresentation[] response = RestClient.builder().baseUrl(keycloakUrl) + .defaultHeader("Authorization", bearer) + .build() + .get() + .uri("/admin/realms/{realmName}/users?username={username}", realmName, username) + .retrieve() + .body(UserRepresentation[].class); + + if (response == null || response.length == 0) { + throw new UserNotFoundException("User not found"); + } + return response[0].id; + } + + static public void setRoleToUser(String username, String roleName, String bearer) throws RoleNotFoundException, UserNotFoundException { + RoleRepresentation roleRepresentation = getRoleRepresentationByName(roleName, bearer); + String userId = getUserIdByName(username, bearer); + + + RestClient.builder().baseUrl(keycloakUrl) + .defaultHeader("Authorization", bearer) + .build() + .post() + .uri("/admin/realms/${realmName}/users/${userId}/role-mappings/realm", realmName, userId) + .body(roleRepresentation) + .contentType(APPLICATION_JSON) + .retrieve(); + } +} + + +class RoleRepresentation { + public String id; + public String name; + public String description; +} + +class UserRepresentation { + public String id; + public String name; +} + diff --git a/front/MyINPulse-front/src/services/api.ts b/front/MyINPulse-front/src/services/api.ts index 091455c..5c4fc7b 100644 --- a/front/MyINPulse-front/src/services/api.ts +++ b/front/MyINPulse-front/src/services/api.ts @@ -14,7 +14,8 @@ axiosInstance.interceptors.response.use( async (error) => { const originalRequest = error.config; if ( - error.response.status === 401 && + ((error.response && error.response.status === 401) || + error.code == "ERR_NETWORK") && !originalRequest._retry && store.authenticated ) { diff --git a/front/MyINPulse-front/src/views/test.vue b/front/MyINPulse-front/src/views/test.vue new file mode 100644 index 0000000..9d98958 --- /dev/null +++ b/front/MyINPulse-front/src/views/test.vue @@ -0,0 +1,73 @@ + + + + + From e26f8da66289199788ad8aaad1b55f62d7d6e44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Le=20Lez?= Date: Wed, 12 Feb 2025 18:51:27 +0100 Subject: [PATCH 2/6] fix: inserting data in db --- .../controller/ComptesRendusController.java | 2 +- .../controller/EntrepreneursController.java | 2 +- .../controller/ProjetsController.java | 2 +- .../controller/RendezVousController.java | 8 +-- .../controller/SectionsController.java | 2 +- .../controller/UtilisateursController.java | 2 +- .../postgres_db/model/Administrateurs.java | 2 +- .../postgres_db/model/Entrepreneurs.java | 2 +- .../postgres_db/model/RendezVous.java | 18 +++--- .../postgres_db/model/Utilisateurs.java | 2 +- MyINPulse-back/src/main/resources/data.sql | 62 ++++++++++--------- MyINPulse-back/src/main/resources/delete.sql | 1 + MyINPulse-back/src/main/resources/schema.sql | 12 ++-- 13 files changed, 61 insertions(+), 56 deletions(-) create mode 100644 MyINPulse-back/src/main/resources/delete.sql diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/ComptesRendusController.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/ComptesRendusController.java index 312ae9e..3862118 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/ComptesRendusController.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/ComptesRendusController.java @@ -42,6 +42,6 @@ public class ComptesRendusController { if (contenu_compte_rendu != null) { compteRendu.get().setContenu_compte_rendu(contenu_compte_rendu); } - return compteRendu.get(); + return this.comptesRendusRepository.save(compteRendu.get()); } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/EntrepreneursController.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/EntrepreneursController.java index f789e0d..8fdc1dd 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/EntrepreneursController.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/EntrepreneursController.java @@ -51,6 +51,6 @@ public class EntrepreneursController { if (status_snee != null) { entrepreneur.get().setStatus_snee(status_snee); } - return entrepreneur.get(); + return this.entrepreneursRepository.save(entrepreneur.get()); } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/ProjetsController.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/ProjetsController.java index f3a469c..d0909b0 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/ProjetsController.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/ProjetsController.java @@ -57,6 +57,6 @@ public class ProjetsController { if (status_projet != null) { projet.get().setStatus_projet(status_projet); } - return projet.get(); + return this.projetsRepository.save(projet.get()); } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/RendezVousController.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/RendezVousController.java index 6f2a39f..9761fd9 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/RendezVousController.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/RendezVousController.java @@ -3,7 +3,7 @@ package enseirb.myinpulse.postgres_db.controller; import enseirb.myinpulse.postgres_db.model.RendezVous; import enseirb.myinpulse.postgres_db.repository.RendezVousRepository; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -39,8 +39,8 @@ public class RendezVousController { public RendezVous updateRendezVous( @PathVariable Long id, LocalDate date_rdv, - LocalDateTime heure_rdv, - LocalDateTime duree_rdv, + LocalTime heure_rdv, + LocalTime duree_rdv, String lieu_rdv, String sujet_rdv) { Optional rendezVous = this.rendezVousRepository.findById(id); @@ -62,6 +62,6 @@ public class RendezVousController { if (sujet_rdv != null) { rendezVous.get().setSujet_rdv(sujet_rdv); } - return rendezVous.get(); + return this.rendezVousRepository.save(rendezVous.get()); } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/SectionsController.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/SectionsController.java index 5b4df39..01f94ae 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/SectionsController.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/SectionsController.java @@ -53,6 +53,6 @@ public class SectionsController { if (date_modification != null) { section.get().setDate_modification(date_modification); } - return section.get(); + return this.sectionsRepository.save(section.get()); } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/UtilisateursController.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/UtilisateursController.java index a6c128d..c46d688 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/UtilisateursController.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/controller/UtilisateursController.java @@ -60,6 +60,6 @@ public class UtilisateursController { if (numero_telephone != null) { utilisateur.get().setNumero_telephone(numero_telephone); } - return utilisateur.get(); + return this.utilisateursRepository.save(utilisateur.get()); } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Administrateurs.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Administrateurs.java index 30b70d1..0f91f9a 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Administrateurs.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Administrateurs.java @@ -8,7 +8,7 @@ import java.util.List; @Entity @Table(name = "administrateurs") -@PrimaryKeyJoinColumn(name = "id_administrateur") +@PrimaryKeyJoinColumn(name = "id_administrateur", referencedColumnName = "id_utilisateur") public class Administrateurs extends Utilisateurs { @ManyToOne(fetch = FetchType.LAZY) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Entrepreneurs.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Entrepreneurs.java index 9b696f3..dbe9a81 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Entrepreneurs.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Entrepreneurs.java @@ -6,7 +6,7 @@ import jakarta.persistence.Table; @Entity @Table(name = "entrepreneurs") -@PrimaryKeyJoinColumn(name = "id_entrepreneur") +@PrimaryKeyJoinColumn(name = "id_entrepreneur", referencedColumnName = "id_utilisateur") public class Entrepreneurs extends Utilisateurs { @Column(length = 255) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/RendezVous.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/RendezVous.java index e1625e5..3bf380d 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/RendezVous.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/RendezVous.java @@ -4,7 +4,7 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; @@ -19,9 +19,9 @@ public class RendezVous { private LocalDate date_rdv; - private LocalDateTime heure_rdv; + private LocalTime heure_rdv; - private LocalDateTime duree_rdv; + private LocalTime duree_rdv; @Column(length = 255) private String lieu_rdv; @@ -51,8 +51,8 @@ public class RendezVous { public RendezVous( Long id_rdv, LocalDate date_rdv, - LocalDateTime heure_rdv, - LocalDateTime duree_rdv, + LocalTime heure_rdv, + LocalTime duree_rdv, String lieu_rdv, String sujet_rdv) { this.id_rdv = id_rdv; @@ -79,19 +79,19 @@ public class RendezVous { this.date_rdv = date_rdv; } - public LocalDateTime getHeure_rdv() { + public LocalTime getHeure_rdv() { return heure_rdv; } - public void setHeure_rdv(LocalDateTime heure_rdv) { + public void setHeure_rdv(LocalTime heure_rdv) { this.heure_rdv = heure_rdv; } - public LocalDateTime getDuree_rdv() { + public LocalTime getDuree_rdv() { return duree_rdv; } - public void setDuree_rdv(LocalDateTime duree_rdv) { + public void setDuree_rdv(LocalTime duree_rdv) { this.duree_rdv = duree_rdv; } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Utilisateurs.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Utilisateurs.java index 9d81163..478b9ef 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Utilisateurs.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/postgres_db/model/Utilisateurs.java @@ -25,7 +25,7 @@ public class Utilisateurs { @Column(length = 255) private String mail_secondaire; - @Column(length = 15) + @Column(length = 20) private String numero_telephone; public Utilisateurs() {} diff --git a/MyINPulse-back/src/main/resources/data.sql b/MyINPulse-back/src/main/resources/data.sql index 4014267..eb4eb9a 100644 --- a/MyINPulse-back/src/main/resources/data.sql +++ b/MyINPulse-back/src/main/resources/data.sql @@ -1,3 +1,5 @@ +TRUNCATE projets, utilisateurs, entrepreneurs, sections, rendez_vous, comptes_rendus CASCADE; + INSERT INTO projets (nom_projet, logo, date_creation, status_projet) VALUES ('Eau du robinet', decode('013d7d16d7ad4fefb61bd95b765c8ceb', 'hex'), TO_DATE('01-OCT-2023', 'DD-MON-YYYY'), 'En cours'), ('Air oxygéné', decode('150647a0984e8f228cd14b54', 'hex'), TO_DATE('04-APR-2024', 'DD-MON-YYYY'), 'En cours'), @@ -9,42 +11,44 @@ INSERT INTO utilisateurs (nom_utilisateur, prenom_utilisateur, mail_principal, m ('Martin', 'Matin', 'genial@mail.fr', 'genial2@mail.fr', '06 52 14 58 73'), ('Charvet', 'Lautre', 'mieux@tmail.fr', 'mieux2@tmail.fr', '07 49 82 16 35'), ('Leguez', 'Theo', 'bof@mesmails.fr', 'bof2@mesmails.fr', '+33 6 78 14 25 29'), -('Kia', 'Bi', 'special@mail.fr', 'special2@mail.fr', '07 65 31 38 95'); +('Kia', 'Bi', 'special@mail.fr', 'special2@mail.fr', '07 65 31 38 95'), +('Ducaillou', 'Pierre', 'maildefou@xyz.fr', 'maildefou2@xyz.fr', '06 54 78 12 62'); -INSERT INTO entrepreneurs (ecole, filiere, status_snee) VALUES -('ENSEIRB-MATMECA', 'INFO', TRUE), -('ENSC', 'Cognitique', TRUE), -('ENSEIRB-MATMECA', 'MATMECA', FALSE), -('SupOptique', 'Classique', TRUE), -('ENSEGID', 'Géoscience', FALSE), -('ENSMAC', 'Matériaux composites - Mécanique', FALSE); + +INSERT INTO entrepreneurs (ecole, filiere, status_snee, id_entrepreneur) VALUES +('ENSEIRB-MATMECA', 'INFO', TRUE, 1), +('ENSC', 'Cognitique', TRUE, 2), +('ENSEIRB-MATMECA', 'MATMECA', FALSE, 3), +('SupOptique', 'Classique', TRUE, 4), +('ENSEGID', 'Géoscience', FALSE, 5), +('ENSMAC', 'Matériaux composites - Mécanique', FALSE, 6); INSERT INTO sections (titre, contenu_section, date_modification) VALUES -("Problème", "les problèmes...", TO_DATE('15-JAN-2025', 'DD-MON-YYYY')), -("Segment de client", "Le segment AB passant le client n°8 est de longueur 32mm. - Le segment BC a quant à lui un longueur de 28mm. Quelle la longueur du segment AC ?", TO_DATE('12-OCT-2022', 'DD-MON-YYYY')), -("Proposition de valeur unique", "'Son prix est de 2594€' 'Ah oui c'est unique en effet'", TO_DATE('25-MAY-2024', 'DD-MON-YYYY')), -("Solution", "Un problème ? Une solution", TO_DATE('08-FEB-2024', 'DD-MON-YYYY')), -("Canaux", "Ici nous avons la Seine, là-bas le Rhin, oh et plus loin le canal de Suez", TO_DATE('19-JUL-2023', 'DD-MON-YYYY')), -("Sources de revenus", "Y'en n'a pas on est pas payé. Enfin y'a du café quoi", TO_DATE('12-JAN-2025', 'DD-MON-YYYY')), -("Structure des coûts", "'Ah oui là ça va faire au moins 1000€ par mois', Eirbware", TO_DATE('06-FEB-2025', 'DD-MON-YYYY')), -("Indicateurs clés", "On apprend les clés comme des badges, ça se fait", TO_DATE('05-FEB-2025', 'DD-MON-YYYY')), -("Avantages concurrentiel", "On est meilleur", TO_DATE('23-APR-2024', 'DD-MON-YYYY')); +('Problème', 'les problèmes...', TO_TIMESTAMP('15-JAN-2025 09:30:20', 'DD-MON-YYYY, HH24:MI:SS')), +('Segment de client', 'Le segment AB passant le client n°8 est de longueur 32mm. + Le segment BC a quant à lui un longueur de 28mm. Quelle la longueur du segment AC ?', TO_TIMESTAMP('12-OCT-2022 17:47:38', 'DD-MON-YYYY, HH24:MI:SS')), +('Proposition de valeur unique', '''Son prix est de 2594€'' ''Ah oui c''est unique en effet', TO_TIMESTAMP('25-MAY-2024 11:12:04', 'DD-MON-YYYY, HH24:MI:SS')), +('Solution', 'Un problème ? Une solution', TO_TIMESTAMP('08-FEB-2024 10:17:53', 'DD-MON-YYYY, HH24:MI:SS')), +('Canaux', 'Ici nous avons la Seine, là-bas le Rhin, oh et plus loin le canal de Suez', TO_TIMESTAMP('19-JUL-2023 19:22:45', 'DD-MON-YYYY, HH24:MI:SS')), +('Sources de revenus', 'Y''en n''a pas on est pas payé. Enfin y''a du café quoi', TO_TIMESTAMP('12-JAN-2025 11:40:26', 'DD-MON-YYYY, HH24:MI:SS')), +('Structure des coûts', '''Ah oui là ça va faire au moins 1000€ par mois'', Eirbware', TO_TIMESTAMP('06-FEB-2025 13:04:06', 'DD-MON-YYYY, HH24:MI:SS')), +('Indicateurs clés', 'On apprend les clés comme des badges, ça se fait', TO_TIMESTAMP('05-FEB-2025 12:42:38', 'DD-MON-YYYY, HH24:MI:SS')), +('Avantages concurrentiel', 'On est meilleur', TO_TIMESTAMP('23-APR-2024 16:24:02', 'DD-MON-YYYY, HH24:MI:SS')); INSERT INTO rendez_vous (date_rdv, heure_rdv, duree_rdv, lieu_rdv, sujet_rdv) VALUES -(TO_DATE('24-DEC-2023', 'DD-MON-YYYY'), '00:00:00', '00:37:53', "À la maison", "Ouvrir les cadeaux"), -(TO_DATE('15-AUG-2024', 'DD-MON-YYYY'), '22:35:00', '00:12:36', "Sur les quais ou dans un champ probablement", "BOUM BOUM les feux d'artifices (on fête quoi déjà ?)"), -(TO_DATE('29-FEB-2023', 'DD-MON-YYYY'), '14:20:00', '00:20:00', "Salle TD 15", "Ah mince c'est pas une année bissextile !"), -(TO_DATE('23-JAN-2024', 'DD-MON-YYYY'), '12:56:27', '11:03:33', "Là où le vent nous porte", "Journée la plus importante de l'année"), -(TO_DATE('25-AUG-2025', 'DD-MON-YYYY'), '00:09:00', '01:00:00', "Euh c'est par où l'amphi 56 ?", "Rentrée scolaire (il fait trop froid c'est quoi ça on est en août)"); +(TO_DATE('24-DEC-2023', 'DD-MON-YYYY'), '00:00:00', '00:37:53', 'À la maison', 'Ouvrir les cadeaux'), +(TO_DATE('15-AUG-2024', 'DD-MON-YYYY'), '22:35:00', '00:12:36', 'Sur les quais ou dans un champ probablement', 'BOUM BOUM les feux d''artifices (on fête quoi déjà ?)'), +(TO_DATE('28-FEB-2023', 'DD-MON-YYYY'), '14:20:00', '00:20:00', 'Salle TD 15', 'Ah mince c''est pas une année bissextile !'), +(TO_DATE('23-JAN-2024', 'DD-MON-YYYY'), '12:56:27', '11:03:33', 'Là où le vent nous porte', 'Journée la plus importante de l''année'), +(TO_DATE('25-AUG-2025', 'DD-MON-YYYY'), '00:09:00', '01:00:00', 'Euh c''est par où l''amphi 56 ?', 'Rentrée scolaire (il fait trop froid c''est quoi ça on est en août)'); INSERT INTO comptes_rendus (contenu_compte_rendu) VALUES -("Ah oui ça c'est super, ah ouais j'aime bien, bien vu de penser à ça"), -("Bonne réunion"), -("Ouais, j'ai rien compris mais niquel on fait comme vous avez dit"), -("Non non ça va pas du tout ce que tu me proposes, faut tout refaire"), -("Réponse de la DSI : non"), -("Trop dommage qu'Apple ait sorti leur logiciel avant nous, on avait la même idée et tout on aurait tellement pu leur faire de la concurrence"); +('Ah oui ça c''est super, ah ouais j''aime bien, bien vu de penser à ça'), +('Bonne réunion'), +('Ouais, j''ai rien compris mais niquel on fait comme vous avez dit'), +('Non non ça va pas du tout ce que tu me proposes, faut tout refaire'), +('Réponse de la DSI : non'), +('Trop dommage qu''Apple ait sorti leur logiciel avant nous, on avait la même idée et tout on aurait tellement pu leur faire de la concurrence'); diff --git a/MyINPulse-back/src/main/resources/delete.sql b/MyINPulse-back/src/main/resources/delete.sql new file mode 100644 index 0000000..c494872 --- /dev/null +++ b/MyINPulse-back/src/main/resources/delete.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS administrateurs, projets, utilisateurs, entrepreneurs, sections, rendez_vous, comptes_rendus, concerner CASCADE; \ No newline at end of file diff --git a/MyINPulse-back/src/main/resources/schema.sql b/MyINPulse-back/src/main/resources/schema.sql index 4916414..f1f8b08 100644 --- a/MyINPulse-back/src/main/resources/schema.sql +++ b/MyINPulse-back/src/main/resources/schema.sql @@ -24,21 +24,21 @@ nom_utilisateur VARCHAR(255) , prenom_utilisateur VARCHAR(255) , mail_principal VARCHAR(255) , mail_secondaire VARCHAR(255) , -numero_telephone VARCHAR(15) , +numero_telephone VARCHAR(20) , CONSTRAINT pk_utilisateur PRIMARY KEY (id_utilisateur) ); CREATE TABLE entrepreneurs ( -id_entrepreneur SERIAL REFERENCES utilisateurs (id_utilisateur), ecole VARCHAR(255) , filiere VARCHAR(255) , status_snee BOOLEAN , -CONSTRAINT pk_entrepreneur PRIMARY KEY (id_entrepreneur) ); +CONSTRAINT pk_entrepreneur PRIMARY KEY (id_utilisateur), +INHERITS (utilisateurs) ); CREATE TABLE administrateurs -( -id_administrateur SERIAL REFERENCES utilisateurs (id_utilisateur), -CONSTRAINT pk_administrateur PRIMARY KEY (id_administrateur) ); +( +CONSTRAINT pk_administrateur PRIMARY KEY (id_utilisateur), +INHERITS (utilisateurs) ); CREATE TABLE sections ( From 6235fe7e6811b6f89ed2d662791cc1d6a04d12e2 Mon Sep 17 00:00:00 2001 From: Pierre Tellier Date: Tue, 18 Feb 2025 12:07:07 +0100 Subject: [PATCH 3/6] feat: separated class definition --- .../enseirb/myinpulse/utils/KeycloakApi.java | 77 ------------------- .../datatypes/RoleRepresentation.java | 7 ++ .../datatypes/UserRepresentation.java | 6 ++ 3 files changed, 13 insertions(+), 77 deletions(-) delete mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/utils/KeycloakApi.java create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/datatypes/RoleRepresentation.java create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/datatypes/UserRepresentation.java diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/KeycloakApi.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/KeycloakApi.java deleted file mode 100644 index e91e9e1..0000000 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/KeycloakApi.java +++ /dev/null @@ -1,77 +0,0 @@ -package enseirb.myinpulse.utils; - -import enseirb.myinpulse.exceptions.UserNotFoundException; -import org.springframework.web.client.RestClient; - -import javax.management.relation.RoleNotFoundException; - -import static org.springframework.http.MediaType.APPLICATION_JSON; - -public class KeycloakApi { - - static final String keycloakUrl = "http://localhost:7080"; - static final String realmName = "test"; - - /** - * Uses Keycloak API to retrieve a role representation of a role by its name - * @param roleName name of the role - * @param bearer authorization header used by the client to authenticate to keycloak - */ - static public RoleRepresentation getRoleRepresentationByName(String roleName, String bearer) throws RoleNotFoundException { - RoleRepresentation[] response = RestClient.builder().baseUrl(keycloakUrl) - .defaultHeader("Authorization", bearer) - .build() - .get() - .uri("/admin/realms/{realmName}/roles/{roleName}", realmName, roleName) - .retrieve() - .body(RoleRepresentation[].class); - - if (response == null || response.length == 0) { - throw new RoleNotFoundException("Role not found"); - } - return response[0]; - } - - static public String getUserIdByName(String username, String bearer) throws UserNotFoundException { - UserRepresentation[] response = RestClient.builder().baseUrl(keycloakUrl) - .defaultHeader("Authorization", bearer) - .build() - .get() - .uri("/admin/realms/{realmName}/users?username={username}", realmName, username) - .retrieve() - .body(UserRepresentation[].class); - - if (response == null || response.length == 0) { - throw new UserNotFoundException("User not found"); - } - return response[0].id; - } - - static public void setRoleToUser(String username, String roleName, String bearer) throws RoleNotFoundException, UserNotFoundException { - RoleRepresentation roleRepresentation = getRoleRepresentationByName(roleName, bearer); - String userId = getUserIdByName(username, bearer); - - - RestClient.builder().baseUrl(keycloakUrl) - .defaultHeader("Authorization", bearer) - .build() - .post() - .uri("/admin/realms/${realmName}/users/${userId}/role-mappings/realm", realmName, userId) - .body(roleRepresentation) - .contentType(APPLICATION_JSON) - .retrieve(); - } -} - - -class RoleRepresentation { - public String id; - public String name; - public String description; -} - -class UserRepresentation { - public String id; - public String name; -} - diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/datatypes/RoleRepresentation.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/datatypes/RoleRepresentation.java new file mode 100644 index 0000000..a6252a9 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/datatypes/RoleRepresentation.java @@ -0,0 +1,7 @@ +package enseirb.myinpulse.utils.keycloak.datatypes; + +public class RoleRepresentation { + public String id; + public String name; + public String description; +} diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/datatypes/UserRepresentation.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/datatypes/UserRepresentation.java new file mode 100644 index 0000000..f2c3522 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/datatypes/UserRepresentation.java @@ -0,0 +1,6 @@ +package enseirb.myinpulse.utils.keycloak.datatypes; + +public class UserRepresentation { + public String id; + public String name; +} From 86e7dc7c757412cc22be9f055887992f06b7b76a Mon Sep 17 00:00:00 2001 From: Pierre Tellier Date: Tue, 18 Feb 2025 16:37:55 +0100 Subject: [PATCH 4/6] fix: removed the test file that was causing the linter to fail --- front/MyINPulse-front/src/views/test.vue | 73 ------------------------ 1 file changed, 73 deletions(-) delete mode 100644 front/MyINPulse-front/src/views/test.vue diff --git a/front/MyINPulse-front/src/views/test.vue b/front/MyINPulse-front/src/views/test.vue deleted file mode 100644 index 9d98958..0000000 --- a/front/MyINPulse-front/src/views/test.vue +++ /dev/null @@ -1,73 +0,0 @@ - - - - - From 5e8e875a37c26bb7672b27801b6ad5ff2fb2bb02 Mon Sep 17 00:00:00 2001 From: Pierre Tellier Date: Tue, 18 Feb 2025 16:45:41 +0100 Subject: [PATCH 5/6] feat: added user deletion and custom api call in the frontend --- .../myinpulse/utils/keycloak/KeycloakApi.java | 135 ++++++++++++++++++ .../src/views/testComponent.vue | 11 ++ 2 files changed, 146 insertions(+) create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/KeycloakApi.java diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/KeycloakApi.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/KeycloakApi.java new file mode 100644 index 0000000..ff6dde2 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/utils/keycloak/KeycloakApi.java @@ -0,0 +1,135 @@ +package enseirb.myinpulse.utils.keycloak; + +import static org.springframework.http.MediaType.APPLICATION_JSON; + +import enseirb.myinpulse.exceptions.UserNotFoundException; +import enseirb.myinpulse.utils.keycloak.datatypes.RoleRepresentation; +import enseirb.myinpulse.utils.keycloak.datatypes.UserRepresentation; + +import org.springframework.web.client.RestClient; + +import javax.management.relation.RoleNotFoundException; + +public class KeycloakApi { + + static final String keycloakUrl; + static final String realmName; + + static { + if (System.getenv("VITE_KEYCLOAK_URL") == null) { + System.exit(-1); + } + keycloakUrl = System.getenv("VITE_KEYCLOAK_URL"); + } + + static { + if (System.getenv("VITE_KEYCLOAK_REALM") == null) { + System.exit(-1); + } + realmName = System.getenv("VITE_KEYCLOAK_REALM"); + } + + /** + * Uses Keycloak API to retrieve a role representation of a role by its name + * + * @param roleName name of the role + * @param bearer authorization header used by the client to authenticate to keycloak + */ + public static RoleRepresentation getRoleRepresentationByName(String roleName, String bearer) + throws RoleNotFoundException { + RoleRepresentation[] response = + RestClient.builder() + .baseUrl(keycloakUrl) + .defaultHeader("Authorization", bearer) + .build() + .get() + .uri("/admin/realms/{realmName}/roles/{roleName}", realmName, roleName) + .retrieve() + .body(RoleRepresentation[].class); + + if (response == null || response.length == 0) { + throw new RoleNotFoundException("Role not found"); + } + return response[0]; + } + + /** + * Use keycloak API to to retreive a userID via his name or email. + * + * @param username username or mail of the user + * @param bearer bearer of the user, allowing access to database + * @return the userid, as a String + * @throws UserNotFoundException + */ + public static String getUserIdByName(String username, String bearer) + throws UserNotFoundException { + UserRepresentation[] response = + RestClient.builder() + .baseUrl(keycloakUrl) + .defaultHeader("Authorization", bearer) + .build() + .get() + .uri( + "/admin/realms/{realmName}/users?username={username}", + realmName, + username) + .retrieve() + .body(UserRepresentation[].class); + + if (response == null || response.length == 0) { + throw new UserNotFoundException("User not found"); + } + return response[0].id; + } + + /** + * TODO: check for error + * + *

Set a keycloak role to a keycloak user. + * + *

Usual roles should be `MyINPulse-admin` and `MyINPulse-entrepreneur` + * + * @param username + * @param roleName + * @param bearer + * @throws RoleNotFoundException + * @throws UserNotFoundException + */ + public static void setRoleToUser(String username, String roleName, String bearer) + throws RoleNotFoundException, UserNotFoundException { + RoleRepresentation roleRepresentation = getRoleRepresentationByName(roleName, bearer); + String userId = getUserIdByName(username, bearer); + + RestClient.builder() + .baseUrl(keycloakUrl) + .defaultHeader("Authorization", bearer) + .build() + .post() + .uri( + "/admin/realms/${realmName}/users/${userId}/role-mappings/realm", + realmName, + userId) + .body(roleRepresentation) + .contentType(APPLICATION_JSON) + .retrieve(); + } + + /** + * Delete a user from Keycloak database. TODO: check the bearer permission. + * + * @param username + * @param bearer + * @throws UserNotFoundException + */ + public static void deleteUser(String username, String bearer) throws UserNotFoundException { + String userId = getUserIdByName(username, bearer); + + RestClient.builder() + .baseUrl(keycloakUrl) + .defaultHeader("Authorization", bearer) + .build() + .delete() + .uri("/admin/realms/${realmName}/users/${userId}", realmName, userId) + .retrieve(); + } +} diff --git a/front/MyINPulse-front/src/views/testComponent.vue b/front/MyINPulse-front/src/views/testComponent.vue index 9ba1d5a..a73d879 100644 --- a/front/MyINPulse-front/src/views/testComponent.vue +++ b/front/MyINPulse-front/src/views/testComponent.vue @@ -1,6 +1,9 @@ From 820757c836aaf568633700b8b81c5b3ec4f80ba2 Mon Sep 17 00:00:00 2001 From: Pierre Tellier Date: Tue, 18 Feb 2025 16:59:19 +0100 Subject: [PATCH 6/6] fix: corrected formatter error --- .../enseirb/myinpulse/api/GetUserInfo.java | 6 +- .../security/KeycloakJwtRolesConverter.java | 80 ++++++++++--------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/api/GetUserInfo.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/api/GetUserInfo.java index 7baea8e..694532f 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/api/GetUserInfo.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/api/GetUserInfo.java @@ -3,13 +3,12 @@ package enseirb.myinpulse.api; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; - -import javax.management.relation.RoleNotFoundException; -import java.security.Principal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import java.security.Principal; +import javax.management.relation.RoleNotFoundException; @SpringBootApplication @RestController @@ -26,7 +25,6 @@ public class GetUserInfo { @GetMapping("/unauth/random") public boolean rand(@RequestHeader("Authorization") String token) throws RoleNotFoundException { System.err.println(token); - System.err.println("HELLO"); return Math.random() > 0.5; } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java index 08c5cab..743b765 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java @@ -4,6 +4,8 @@ */ package enseirb.myinpulse.security; +import static java.util.stream.Collectors.toSet; + import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; @@ -18,41 +20,35 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.stream.Collectors.toSet; - - public class KeycloakJwtRolesConverter implements Converter { - /** - * Prefix used for realm level roles. - */ + /** Prefix used for realm level roles. */ public static final String PREFIX_REALM_ROLE = "ROLE_REALM_"; - /** - * Prefix used in combination with the resource (client) name for resource level roles. - */ + + /** Prefix used in combination with the resource (client) name for resource level roles. */ public static final String PREFIX_RESOURCE_ROLE = "ROLE_"; - /** - * Name of the claim containing the realm level roles - */ + /** Name of the claim containing the realm level roles */ private static final String CLAIM_REALM_ACCESS = "realm_access"; - /** - * Name of the claim containing the resources (clients) the user has access to. - */ + + /** Name of the claim containing the resources (clients) the user has access to. */ private static final String CLAIM_RESOURCE_ACCESS = "resource_access"; - /** - * Name of the claim containing roles. (Applicable to realm and resource level.) - */ + + /** Name of the claim containing roles. (Applicable to realm and resource level.) */ private static final String CLAIM_ROLES = "roles"; @Override public AbstractAuthenticationToken convert(Jwt source) { - return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source) - .stream(), tokenRolesExtractor(source).stream()) - .collect(toSet())); + return new JwtAuthenticationToken( + source, + Stream.concat( + new JwtGrantedAuthoritiesConverter().convert(source).stream(), + tokenRolesExtractor(source).stream()) + .collect(toSet())); } /** - * Extracts the realm and resource level roles from a JWT token distinguishing between them using prefixes. + * Extracts the realm and resource level roles from a JWT token distinguishing between them + * using prefixes. */ public Collection tokenRolesExtractor(Jwt jwt) { // Collection that will hold the extracted roles @@ -69,33 +65,43 @@ public class KeycloakJwtRolesConverter implements Converter realmRoles = roles.stream() - // Prefix all realm roles with "ROLE_realm_" - .map(role -> new SimpleGrantedAuthority(PREFIX_REALM_ROLE + role)) - .collect(Collectors.toList()); + Collection realmRoles = + roles.stream() + // Prefix all realm roles with "ROLE_realm_" + .map(role -> new SimpleGrantedAuthority(PREFIX_REALM_ROLE + role)) + .collect(Collectors.toList()); grantedAuthorities.addAll(realmRoles); } } // Resource (client) roles - // A user might have access to multiple resources all containing their own roles. Therefore, it is a map of + // A user might have access to multiple resources all containing their own roles. Therefore, + // it is a map of // resource each possibly containing a "roles" property. - Map>> resourceAccess = jwt.getClaim(CLAIM_RESOURCE_ACCESS); + Map>> resourceAccess = + jwt.getClaim(CLAIM_RESOURCE_ACCESS); // Check if resources are assigned if (resourceAccess != null && !resourceAccess.isEmpty()) { // Iterate of all the resources - resourceAccess.forEach((resource, resourceClaims) -> { - // Iterate of the "roles" claim inside the resource claims - resourceClaims.get(CLAIM_ROLES).forEach( - // Add the role to the granted authority prefixed with ROLE_ and the name of the resource - role -> grantedAuthorities.add(new SimpleGrantedAuthority(PREFIX_RESOURCE_ROLE + resource + "_" + role)) - ); - }); + resourceAccess.forEach( + (resource, resourceClaims) -> { + // Iterate of the "roles" claim inside the resource claims + resourceClaims + .get(CLAIM_ROLES) + .forEach( + // Add the role to the granted authority prefixed with ROLE_ + // and the name of the resource + role -> + grantedAuthorities.add( + new SimpleGrantedAuthority( + PREFIX_RESOURCE_ROLE + + resource + + "_" + + role))); + }); } return grantedAuthorities; } - - }