feature/318_signalR_authorization do větve development před před 3 roky
| using Diligent.WebAPI.Business.MongoServices; | |||||
| using Diligent.WebAPI.Data; | |||||
| using Diligent.WebAPI.Data.Entities; | |||||
| using Microsoft.Extensions.Options; | |||||
| using MongoDB.Driver; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Diligent.WebAPI.Business.Services | |||||
| { | |||||
| public class AuthorizationService : BaseMongo<Customer> | |||||
| { | |||||
| public AuthorizationService(IOptions<WebApiDatabaseSettings> webApiDatabaseSettings) : | |||||
| base(webApiDatabaseSettings, "Customer") | |||||
| { } | |||||
| public async Task<Customer> GetByUserName(string username) | |||||
| { | |||||
| return await _mongoCollection.Find(c => c.UserName == username).FirstOrDefaultAsync(); | |||||
| } | |||||
| } | |||||
| } |
| using AutoMapper; | using AutoMapper; | ||||
| using Diligent.WebAPI.Business.Services; | using Diligent.WebAPI.Business.Services; | ||||
| using Diligent.WebAPI.Data.Entities; | using Diligent.WebAPI.Data.Entities; | ||||
| using Microsoft.AspNetCore.Authorization; | |||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||
| namespace Diligent.WebAPI.Host.Controllers | namespace Diligent.WebAPI.Host.Controllers | ||||
| } | } | ||||
| [HttpPost] | [HttpPost] | ||||
| [Authorize(Roles = "Customer")] | |||||
| public async Task<IActionResult> CreateChat(Room room) | public async Task<IActionResult> CreateChat(Room room) | ||||
| { | { | ||||
| if(room == null) | if(room == null) |
| using Microsoft.IdentityModel.Tokens; | using Microsoft.IdentityModel.Tokens; | ||||
| using System.Text; | using System.Text; | ||||
| using Diligent.WebAPI.Business.Services; | using Diligent.WebAPI.Business.Services; | ||||
| using Diligent.WebAPI.Host.Middlewares; | |||||
| namespace Diligent.WebAPI.Host.Extensions; | namespace Diligent.WebAPI.Host.Extensions; | ||||
| builder.Services.AddSingleton<InsurerService>(); | builder.Services.AddSingleton<InsurerService>(); | ||||
| builder.Services.AddSingleton<RoomService>(); | builder.Services.AddSingleton<RoomService>(); | ||||
| builder.Services.AddSingleton<RequestService>(); | builder.Services.AddSingleton<RequestService>(); | ||||
| builder.Services.AddSingleton<AuthorizationService>(); | |||||
| builder.Services.AddMediatR(Assembly.GetExecutingAssembly()); | builder.Services.AddMediatR(Assembly.GetExecutingAssembly()); | ||||
| builder.Services.AddSignalR(); | |||||
| builder.Services.AddSignalR(options => | |||||
| { | |||||
| options.EnableDetailedErrors = true; | |||||
| options.AddFilter<AuthorizationHubFilter>(); | |||||
| }); | |||||
| builder.Services.AddSingleton<AuthorizationHubFilter>(); | |||||
| var mongoDbSettings = configuration.GetSection("WebApiDB"); | var mongoDbSettings = configuration.GetSection("WebApiDB"); | ||||
| using MediatR; | using MediatR; | ||||
| using Microsoft.AspNetCore.Authorization; | using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.SignalR; | using Microsoft.AspNetCore.SignalR; | ||||
| using Diligent.WebAPI.Host.Middlewares; | |||||
| namespace Diligent.WebAPI.Host.Hubs | namespace Diligent.WebAPI.Host.Hubs | ||||
| { | { | ||||
| //} | //} | ||||
| [HubMethodName("SendMessageToGroup")] | [HubMethodName("SendMessageToGroup")] | ||||
| [AuthorizationHubFilter(Roles = "Customer,Support")] | |||||
| public async Task SendMessageToGroup(ChatMessage message) | public async Task SendMessageToGroup(ChatMessage message) | ||||
| { | { | ||||
| // Find user's room and send message to everyone | // Find user's room and send message to everyone | ||||
| } | } | ||||
| [HubMethodName("JoinRoom")] | [HubMethodName("JoinRoom")] | ||||
| [AuthorizationHubFilter(Roles = "Customer,Support")] | |||||
| public async Task JoinRoom(UserConnection userConnection) | public async Task JoinRoom(UserConnection userConnection) | ||||
| { | { | ||||
| // Check is user in requested room | // Check is user in requested room | ||||
| } | } | ||||
| [HubMethodName("LeaveRoom")] | [HubMethodName("LeaveRoom")] | ||||
| [AuthorizationHubFilter(Roles = "Customer,Support")] | |||||
| public async Task LeaveRoom(LeaveChatRoomDTO connection) | public async Task LeaveRoom(LeaveChatRoomDTO connection) | ||||
| { | { | ||||
| if (_connections.TryGetValue(connection.ConnId, out UserConnection room)) | if (_connections.TryGetValue(connection.ConnId, out UserConnection room)) |
| using Diligent.WebAPI.Business.Services; | |||||
| using Microsoft.AspNetCore.SignalR; | |||||
| using Microsoft.IdentityModel.Tokens; | |||||
| using System.IdentityModel.Tokens.Jwt; | |||||
| using System.Net; | |||||
| using System.Text; | |||||
| using System.Web.Http; | |||||
| namespace Diligent.WebAPI.Host.Middlewares | |||||
| { | |||||
| [AttributeUsage(AttributeTargets.Method)] | |||||
| public class AuthorizationHubFilter : Attribute, IHubFilter | |||||
| { | |||||
| public string Roles; | |||||
| public async ValueTask<object> InvokeMethodAsync( | |||||
| HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next) | |||||
| { | |||||
| Type type = typeof(AuthorizationHubFilter); | |||||
| var arguments = Attribute.GetCustomAttributes(invocationContext.HubMethod).Where(k => k.TypeId.ToString() == type.ToString()).ToList(); | |||||
| // there is no custom attributes, so the filter does not need to perform authorization | |||||
| if (!arguments.Any()) | |||||
| return await next(invocationContext); | |||||
| // get filter attribute | |||||
| var roles = Attribute.GetCustomAttribute(invocationContext.HubMethod, arguments[0].GetType()); | |||||
| if (roles is null) | |||||
| throw new NullReferenceException(); | |||||
| // get roles from filter attribute and split roles into an array | |||||
| var arrayOfRoles = ((AuthorizationHubFilter)roles).Roles.Split(","); | |||||
| var context = invocationContext.Context.GetHttpContext(); | |||||
| if (context is null) | |||||
| throw new NullReferenceException(); | |||||
| var token = context.Request.Query["access_token"]; | |||||
| await AttachUserToContext(context, token, arrayOfRoles); | |||||
| return await next(invocationContext); | |||||
| } | |||||
| private static async Task AttachUserToContext(HttpContext context, string token, string[] roles) | |||||
| { | |||||
| bool contain = false; | |||||
| try | |||||
| { | |||||
| var authorizationService = context.RequestServices.GetService(typeof(AuthorizationService)); | |||||
| var configuration = context.RequestServices.GetService(typeof(IConfiguration)); | |||||
| if (authorizationService is null || configuration is null) | |||||
| throw new NullReferenceException(); | |||||
| var tokenHandler = new JwtSecurityTokenHandler(); | |||||
| var jwtSettings = ((IConfiguration)configuration).GetSection("JwtSettings"); | |||||
| var issuer = jwtSettings["validIssuer"]; | |||||
| var audience = jwtSettings["validAudience"]; | |||||
| var key = Encoding.UTF8.GetBytes(jwtSettings["jwtSecret"]); | |||||
| tokenHandler.ValidateToken(token, new TokenValidationParameters | |||||
| { | |||||
| ValidateIssuerSigningKey = true, | |||||
| IssuerSigningKey = new SymmetricSecurityKey(key), | |||||
| ValidIssuer = issuer, | |||||
| ValidAudience = audience, | |||||
| ValidateIssuer = true, | |||||
| ValidateAudience = true, | |||||
| ValidateLifetime = true, | |||||
| }, out SecurityToken validatedToken); | |||||
| var jwtToken = (JwtSecurityToken)validatedToken; | |||||
| var userName = jwtToken.Payload["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"].ToString(); | |||||
| var rl = (jwtToken.Payload["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]).ToString(); | |||||
| if (rl is null) | |||||
| throw new Exception(); | |||||
| var rlArray = rl.Split(","); | |||||
| //check is there any role from token same as role from attribute of authorization filter | |||||
| foreach (var t in rlArray) | |||||
| { | |||||
| if (roles.Contains(t)) | |||||
| { | |||||
| contain = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (!contain) | |||||
| { | |||||
| throw new Exception(); | |||||
| } | |||||
| // attach user to context on successful jwt validation | |||||
| if (userName != null) | |||||
| { | |||||
| context.Items["User"] = await ((AuthorizationService)authorizationService).GetByUserName(userName); | |||||
| } | |||||
| } | |||||
| catch | |||||
| { | |||||
| // user don't have appropriate role to access the hub method | |||||
| if (!contain) | |||||
| { | |||||
| throw new HttpResponseException(HttpStatusCode.Forbidden); | |||||
| } | |||||
| throw new UnauthorizedAccessException(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } |
| useEffect(() => { | useEffect(() => { | ||||
| if (user && !loadedNotification) { | if (user && !loadedNotification) { | ||||
| console.log("Ovde"); | |||||
| dispatch(loadNotifications(user.id)); | dispatch(loadNotifications(user.id)); | ||||
| setLoadedNotification((oldState) => !oldState); | setLoadedNotification((oldState) => !oldState); | ||||
| } | } | ||||
| const joinRoom = async (n) => { | const joinRoom = async (n) => { | ||||
| try { | try { | ||||
| const connection = new HubConnectionBuilder() | const connection = new HubConnectionBuilder() | ||||
| .withUrl("http://localhost:5116/chatHub") | |||||
| .withUrl("http://localhost:5116/chatHub",{accessTokenFactory:() => user.token}) | |||||
| .withAutomaticReconnect() | .withAutomaticReconnect() | ||||
| .build(); | .build(); | ||||
| import { createContext, useEffect, useState } from "react"; | import { createContext, useEffect, useState } from "react"; | ||||
| import { useNavigate } from "react-router-dom"; | import { useNavigate } from "react-router-dom"; | ||||
| import axios from "axios"; | |||||
| export const UserContext = createContext(); | export const UserContext = createContext(); | ||||
| const [user, setUser] = useState(null); | const [user, setUser] = useState(null); | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| // if (JSON.parse(localStorage.getItem('user'))) { | |||||
| // axios.defaults.headers.common['Authorization'] = `Bearer ${JSON.parse(localStorage.getItem('user')).token}`; | |||||
| // } | |||||
| if (JSON.parse(localStorage.getItem('user'))) { | |||||
| axios.defaults.headers.common['Authorization'] = `Bearer ${JSON.parse(localStorage.getItem('user')).token}`; | |||||
| } | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (localStorage.getItem("user") !== undefined) { | if (localStorage.getItem("user") !== undefined) { |
| (notification) => notification.roomId !== action.payload | (notification) => notification.roomId !== action.payload | ||||
| ); | ); | ||||
| }, | }, | ||||
| leaveRoom: (state, action) => { | leaveRoom: (state, action) => { | ||||
| state.activeRoom = null; | state.activeRoom = null; | ||||