feat: merged Keycloak / postgres
All checks were successful
Format / formatting (push) Successful in 7s
CI / build (push) Successful in 11s

This commit is contained in:
Pierre Tellier
2025-02-18 17:20:08 +01:00
22 changed files with 258 additions and 84 deletions

View File

@ -6,15 +6,18 @@ import enseirb.myinpulse.postgres_db.repository.ComptesRendusRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.management.relation.RoleNotFoundException;
@SpringBootApplication
@RestController
public class GetUserInfo {
@GetMapping("/unauth/random")
public boolean rand() {
public boolean rand(@RequestHeader("Authorization") String token) throws RoleNotFoundException {
System.err.println(token);
return Math.random() > 0.5;
}

View File

@ -0,0 +1,7 @@
package enseirb.myinpulse.exceptions;
public class RoleNotFoudException extends RuntimeException {
public RoleNotFoudException(String message) {
super(message);
}
}

View File

@ -0,0 +1,7 @@
package enseirb.myinpulse.exceptions;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}

View File

@ -47,6 +47,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());
}
}

View File

@ -53,6 +53,6 @@ public class EntrepreneursController {
if (status_snee != null) {
entrepreneur.get().setStatus_snee(status_snee);
}
return entrepreneur.get();
return this.entrepreneursRepository.save(entrepreneur.get());
}
}

View File

@ -59,6 +59,6 @@ public class ProjetsController {
if (status_projet != null) {
projet.get().setStatus_projet(status_projet);
}
return projet.get();
return this.projetsRepository.save(projet.get());
}
}

View File

@ -2,16 +2,14 @@ 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.LocalTime;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Optional;
@RestController
public class RendezVousController {
@ -41,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> rendezVous = this.rendezVousRepository.findById(id);
@ -64,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());
}
}

View File

@ -55,6 +55,6 @@ public class SectionsController {
if (date_modification != null) {
section.get().setDate_modification(date_modification);
}
return section.get();
return this.sectionsRepository.save(section.get());
}
}

View File

@ -62,6 +62,6 @@ public class UtilisateursController {
if (numero_telephone != null) {
utilisateur.get().setNumero_telephone(numero_telephone);
}
return utilisateur.get();
return this.utilisateursRepository.save(utilisateur.get());
}
}

View File

@ -9,7 +9,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)

View File

@ -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)

View File

@ -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;
@ -36,8 +36,8 @@ public class RendezVous {
private Long id_rdv;
private LocalDate date_rdv;
private LocalDateTime heure_rdv;
private LocalDateTime duree_rdv;
private LocalTime heure_rdv;
private LocalTime duree_rdv;
@Column(length = 255)
private String lieu_rdv;
@ -49,8 +49,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;
@ -77,19 +77,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;
}

View File

@ -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() {}

View File

@ -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 static java.util.stream.Collectors.toSet;
@ -38,7 +42,7 @@ public class KeycloakJwtRolesConverter implements Converter<Jwt, AbstractAuthent
source,
Stream.concat(
new JwtGrantedAuthoritiesConverter().convert(source).stream(),
TEMPORARNAME(source).stream())
tokenRolesExtractor(source).stream())
.collect(toSet()));
}
@ -46,7 +50,7 @@ public class KeycloakJwtRolesConverter implements Converter<Jwt, AbstractAuthent
* Extracts the realm and resource level roles from a JWT token distinguishing between them
* using prefixes.
*/
public Collection<GrantedAuthority> TEMPORARNAME(Jwt jwt) {
public Collection<GrantedAuthority> tokenRolesExtractor(Jwt jwt) {
// Collection that will hold the extracted roles
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();

View File

@ -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
*
* <p>Set a keycloak role to a keycloak user.
*
* <p>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();
}
}

View File

@ -0,0 +1,7 @@
package enseirb.myinpulse.utils.keycloak.datatypes;
public class RoleRepresentation {
public String id;
public String name;
public String description;
}

View File

@ -0,0 +1,6 @@
package enseirb.myinpulse.utils.keycloak.datatypes;
public class UserRepresentation {
public String id;
public String name;
}