backend-api #6
@@ -2,13 +2,13 @@ package enseirb.myinpulse.api;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
					import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
				
			||||||
import org.springframework.security.core.context.SecurityContextHolder;
 | 
					import org.springframework.security.core.context.SecurityContextHolder;
 | 
				
			||||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
					import org.springframework.web.bind.annotation.*;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMethod;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.RestController;
 | 
					 | 
				
			||||||
import org.springframework.web.bind.annotation.CrossOrigin;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.management.relation.RoleNotFoundException;
 | 
				
			||||||
import java.security.Principal;
 | 
					import java.security.Principal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@SpringBootApplication
 | 
					@SpringBootApplication
 | 
				
			||||||
@RestController
 | 
					@RestController
 | 
				
			||||||
public class GetUserInfo {
 | 
					public class GetUserInfo {
 | 
				
			||||||
@@ -22,7 +22,8 @@ public class GetUserInfo {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
 | 
					    @CrossOrigin(methods = {RequestMethod.GET, RequestMethod.OPTIONS})
 | 
				
			||||||
    @GetMapping("/random")
 | 
					    @GetMapping("/random")
 | 
				
			||||||
    public boolean rand(){
 | 
					    public boolean rand(@RequestHeader("Authorization") String token) throws RoleNotFoundException {
 | 
				
			||||||
 | 
					        System.err.println(token);
 | 
				
			||||||
        System.err.println("HELLO");
 | 
					        System.err.println("HELLO");
 | 
				
			||||||
        return Math.random() > 0.5;
 | 
					        return Math.random() > 0.5;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package enseirb.myinpulse.exceptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class RoleNotFoudException extends RuntimeException {
 | 
				
			||||||
 | 
					    public RoleNotFoudException(String message) {
 | 
				
			||||||
 | 
					        super(message);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package enseirb.myinpulse.exceptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class UserNotFoundException extends RuntimeException {
 | 
				
			||||||
 | 
					    public UserNotFoundException(String message) {
 | 
				
			||||||
 | 
					        super(message);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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;
 | 
					package enseirb.myinpulse.security;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.core.convert.converter.Converter;
 | 
					import org.springframework.core.convert.converter.Converter;
 | 
				
			||||||
@@ -41,17 +45,16 @@ public class KeycloakJwtRolesConverter implements Converter<Jwt, AbstractAuthent
 | 
				
			|||||||
    private static final String CLAIM_ROLES = "roles";
 | 
					    private static final String CLAIM_ROLES = "roles";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public AbstractAuthenticationToken convert(Jwt source)
 | 
					    public AbstractAuthenticationToken convert(Jwt source) {
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source)
 | 
					        return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source)
 | 
				
			||||||
                        .stream(), TEMPORARNAME(source).stream())
 | 
					                        .stream(), tokenRolesExtractor(source).stream())
 | 
				
			||||||
                .collect(toSet()));
 | 
					                .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<GrantedAuthority> TEMPORARNAME(Jwt jwt) {
 | 
					    public Collection<GrantedAuthority> tokenRolesExtractor(Jwt jwt) {
 | 
				
			||||||
        // Collection that will hold the extracted roles
 | 
					        // Collection that will hold the extracted roles
 | 
				
			||||||
        Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
 | 
					        Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,7 +14,8 @@ axiosInstance.interceptors.response.use(
 | 
				
			|||||||
    async (error) => {
 | 
					    async (error) => {
 | 
				
			||||||
        const originalRequest = error.config;
 | 
					        const originalRequest = error.config;
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            error.response.status === 401 &&
 | 
					            ((error.response && error.response.status === 401) ||
 | 
				
			||||||
 | 
					                error.code == "ERR_NETWORK") &&
 | 
				
			||||||
            !originalRequest._retry &&
 | 
					            !originalRequest._retry &&
 | 
				
			||||||
            store.authenticated
 | 
					            store.authenticated
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										73
									
								
								front/MyINPulse-front/src/views/test.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								front/MyINPulse-front/src/views/test.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { store } from "../main.ts";
 | 
				
			||||||
 | 
					import { callApi } from "@/services/api.ts";
 | 
				
			||||||
 | 
					import TempModal from "@/components/temp-modal.vue";
 | 
				
			||||||
 | 
					let roleName;
 | 
				
			||||||
 | 
					let username;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <h1>Test page</h1>
 | 
				
			||||||
 | 
					    <table class="test" style="width: 100%">
 | 
				
			||||||
 | 
					        <tbody>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>Is Currently Authenticated ?</td>
 | 
				
			||||||
 | 
					                <td>{{ store.authenticated }}</td>
 | 
				
			||||||
 | 
					                <td><button @click="store.login">Login</button></td>
 | 
				
			||||||
 | 
					                <td><button @click="store.logout">Logout</button></td>
 | 
				
			||||||
 | 
					                <td><button @click="store.signup">Signup</button></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>current token</td>
 | 
				
			||||||
 | 
					                <td>{{ store.user.token }}</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                    <button @click="store.refreshUserToken">Refresh</button>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>Current refresh token</td>
 | 
				
			||||||
 | 
					                <td>{{ store.user.refreshToken }}</td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>Entrepreneur API call</td>
 | 
				
			||||||
 | 
					                <td><button @click="callApi('random')">call</button></td>
 | 
				
			||||||
 | 
					                <td>res</td>
 | 
				
			||||||
 | 
					                <td></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>Admin API call</td>
 | 
				
			||||||
 | 
					                <td><button @click="callApi('random2')">call</button></td>
 | 
				
			||||||
 | 
					                <td>res</td>
 | 
				
			||||||
 | 
					                <td></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td>Unauth API call</td>
 | 
				
			||||||
 | 
					                <td><button @click="callApi('random3')">call</button></td>
 | 
				
			||||||
 | 
					                <td>res</td>
 | 
				
			||||||
 | 
					                <td id="3"></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					    </table>
 | 
				
			||||||
 | 
					    <input v-model="username" />
 | 
				
			||||||
 | 
					    <input v-model="roleName" />
 | 
				
			||||||
 | 
					    <button @click="setRoleToUser(username, roleName)">callAPI</button>
 | 
				
			||||||
 | 
					    <button @click="assignRoleToUser">callAPI2</button>
 | 
				
			||||||
 | 
					    <temp-modal></temp-modal>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					table {
 | 
				
			||||||
 | 
					    width: 100px;
 | 
				
			||||||
 | 
					    table-layout: fixed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					tr {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					td {
 | 
				
			||||||
 | 
					    border: solid 1px black;
 | 
				
			||||||
 | 
					    width: 20%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user