diff --git a/src/IoTHub.Portal.Application/Mappers/AccessControlProfile.cs b/src/IoTHub.Portal.Application/Mappers/AccessControlProfile.cs new file mode 100644 index 000000000..73ab1cc3b --- /dev/null +++ b/src/IoTHub.Portal.Application/Mappers/AccessControlProfile.cs @@ -0,0 +1,34 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Mappers +{ + using AutoMapper; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Shared.Models.v10; + + public class AccessControlProfile : Profile + { + public AccessControlProfile() + { + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id)) + .ForMember(dest => dest.PrincipalId, opts => opts.MapFrom(src => src.PrincipalId)) + .ForMember(dest => dest.Scope, opts => opts.MapFrom(src => src.Scope)) + .ForMember(dest => dest.Role, opts => opts.MapFrom(src => src.Role != null + ? new RoleModel + { + Id = src.Role.Id, + Name = src.Role.Name + } + : null)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.PrincipalId, opts => opts.MapFrom(src => src.PrincipalId)) + .ForMember(dest => dest.Scope, opts => opts.MapFrom(src => src.Scope)) + .ForMember(dest => dest.RoleId, opts => opts.MapFrom(src => src.Role.Id)) + .ForMember(dest => dest.Role, opts => opts.Ignore()); + } + } +} diff --git a/src/IoTHub.Portal.Application/Mappers/ConcentratorProfile.cs b/src/IoTHub.Portal.Application/Mappers/ConcentratorProfile.cs index 3b509d7c9..d365fd451 100644 --- a/src/IoTHub.Portal.Application/Mappers/ConcentratorProfile.cs +++ b/src/IoTHub.Portal.Application/Mappers/ConcentratorProfile.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Application.Mappers using IoTHub.Portal.Domain.Entities; using Microsoft.Azure.Devices.Shared; using Models.v10.LoRaWAN; - using Shared.Models.v1._0; + using Shared.Models.v10; public class ConcentratorProfile : Profile { diff --git a/src/IoTHub.Portal.Application/Mappers/GroupProfile.cs b/src/IoTHub.Portal.Application/Mappers/GroupProfile.cs new file mode 100644 index 000000000..678b7b401 --- /dev/null +++ b/src/IoTHub.Portal.Application/Mappers/GroupProfile.cs @@ -0,0 +1,39 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Mappers +{ + using AutoMapper; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Shared.Models.v10; + + public class GroupProfile : Profile + { + public GroupProfile() + { + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.Color, opts => opts.MapFrom(src => src.Color)) + .ForMember(dest => dest.PrincipalId, opts => opts.MapFrom(src => src.PrincipalId)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.Color, opts => opts.MapFrom(src => src.Color)) + .ForMember(dest => dest.Description, opts => opts.MapFrom(src => src.Description)) + .ForMember(dest => dest.PrincipalId, opts => opts.MapFrom(src => src.PrincipalId)); + + _ = CreateMap() + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.Color, opts => opts.MapFrom(src => src.Color)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.PrincipalId, opt => opt.Ignore()) + .ForMember(dest => dest.Color, opt => opt.MapFrom(src => src.Color)) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)) + .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description)); + } + } +} diff --git a/src/IoTHub.Portal.Application/Mappers/RoleProfile.cs b/src/IoTHub.Portal.Application/Mappers/RoleProfile.cs new file mode 100644 index 000000000..63e756a8f --- /dev/null +++ b/src/IoTHub.Portal.Application/Mappers/RoleProfile.cs @@ -0,0 +1,41 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Mappers +{ + using AutoMapper; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Shared.Models.v10; + + public class RoleProfile : Profile + { + public RoleProfile() + { + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.Color, opts => opts.MapFrom(src => src.Color)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.Color, opts => opts.MapFrom(src => src.Color)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.Color, opts => opts.MapFrom(src => src.Color)) + .ForMember(dest => dest.Description, opts => opts.MapFrom(src => src.Description)) + .ForMember(dest => dest.Actions, opts => opts.MapFrom(src => + src.Actions.Select(a => a.Name))); + + _ = CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.Color, opts => opts.MapFrom(src => src.Color)) + .ForMember(dest => dest.Description, opts => opts.MapFrom(src => src.Description)) + .ForMember(dest => dest.Actions, opts => opts.MapFrom(src => + src.Actions.Select(a => new Action { Name = a }))); + } + } +} diff --git a/src/IoTHub.Portal.Application/Mappers/UserProfile.cs b/src/IoTHub.Portal.Application/Mappers/UserProfile.cs new file mode 100644 index 000000000..62ef0947f --- /dev/null +++ b/src/IoTHub.Portal.Application/Mappers/UserProfile.cs @@ -0,0 +1,45 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Mappers +{ + using AutoMapper; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Shared.Models.v10; + + public class UserProfile : Profile + { + public UserProfile() + { + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id)) + .ForMember(dest => dest.GivenName, opts => opts.MapFrom(src => src.GivenName)) + .ForMember(dest => dest.Email, opts => opts.MapFrom(src => src.Email)) + .ForMember(dest => dest.PrincipalId, opts => opts.MapFrom(src => src.PrincipalId)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.Id)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Name)) + .ForMember(dest => dest.Email, opts => opts.MapFrom(src => src.Email)) + .ForMember(dest => dest.GivenName, opts => opts.MapFrom(src => src.GivenName)) + .ForMember(dest => dest.FamilyName, opts => opts.MapFrom(src => src.FamilyName)) + .ForMember(dest => dest.Avatar, opts => opts.MapFrom(src => src.Avatar)) + .ForMember(dest => dest.PrincipalId, opts => opts.MapFrom(src => src.PrincipalId)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)) + .ForMember(dest => dest.Avatar, opts => opts.MapFrom(src => src.Avatar)) + .ForMember(dest => dest.GivenName, opt => opt.MapFrom(src => src.GivenName)) + .ForMember(dest => dest.FamilyName, opt => opt.MapFrom(src => src.FamilyName)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) + .ForMember(dest => dest.GivenName, opt => opt.MapFrom(src => src.GivenName)) + .ForMember(dest => dest.PrincipalId, opt => opt.MapFrom(src => src.PrincipalId)); + } + } +} diff --git a/src/IoTHub.Portal.Application/Services/AccessControlService.cs b/src/IoTHub.Portal.Application/Services/AccessControlService.cs new file mode 100644 index 000000000..b623aafd3 --- /dev/null +++ b/src/IoTHub.Portal.Application/Services/AccessControlService.cs @@ -0,0 +1,155 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Services +{ + using System.Threading.Tasks; + using AutoMapper; + using IoTHub.Portal.Domain; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Shared.Models.v10; + using IoTHub.Portal.Shared.Models.v10.Filters; + using IoTHub.Portal.Domain.Exceptions; + using IoTHub.Portal.Crosscutting; + + internal class AccessControlService : IAccessControlManagementService + { + private readonly IAccessControlRepository accessControlRepository; + private readonly IUnitOfWork unitOfWork; + private readonly IMapper mapper; + private readonly IRoleRepository roleRepository; + private readonly IPrincipalRepository principalRepository; + + public AccessControlService(IAccessControlRepository accessControlRepository, IUnitOfWork unitOfWork, IMapper mapper, IRoleRepository roleRepository, IPrincipalRepository principalRepository) + { + this.accessControlRepository = accessControlRepository; + this.unitOfWork = unitOfWork; + this.mapper = mapper; + this.roleRepository = roleRepository; + this.principalRepository = principalRepository; + } + + public async Task GetAccessControlAsync(string Id) + { + var acEntity = await this.accessControlRepository.GetByIdAsync(Id, ac => ac.Role); + if (acEntity is null) + { + throw new ResourceNotFoundException($"The AccessControl with the id {Id} doesn't exist"); + } + var acModel = this.mapper.Map(acEntity); + return acModel; + } + + public async Task> GetAccessControlPage( + string? searchKeyword = null, + int pageSize = 10, + int pageNumber = 0, + string[] orderBy = null, + string? principalId = null + ) + { + var acFilter = new AccessControlFilter + { + Keyword = searchKeyword, + PageSize = pageSize, + PageNumber = pageNumber, + OrderBy = orderBy + }; + var acPredicate = PredicateBuilder.True(); + if (!string.IsNullOrWhiteSpace(acFilter.Keyword)) + { + acPredicate = acPredicate.And(ac => ac.Scope.ToLower().Contains(acFilter.Keyword.ToLower()) || ac.Role.Name.ToLower().Contains(acFilter.Keyword.ToLower())); + } + + if (!string.IsNullOrEmpty(principalId)) + { + acPredicate = acPredicate.And(ac => ac.PrincipalId == principalId); + } + + var paginatedAc = await this.accessControlRepository.GetPaginatedListAsync( + pageNumber, + pageSize, + orderBy, + acPredicate + ); + + var paginatedAcDto = new PaginatedResult + { + Data = paginatedAc.Data.Select(x => this.mapper.Map(x)).ToList(), + TotalCount = paginatedAc.TotalCount, + CurrentPage = paginatedAc.CurrentPage, + PageSize = pageSize + }; + return new PaginatedResult(paginatedAcDto.Data, paginatedAcDto.TotalCount); + } + + public async Task CreateAccessControl(AccessControlModel accessControl) + { + if (accessControl is null) + { + throw new ArgumentNullException(nameof(accessControl)); + } + var principal = await this.principalRepository.GetByIdAsync(accessControl.PrincipalId); + if (principal == null) + { + throw new ResourceNotFoundException($"The principal with the id {accessControl.PrincipalId} does'nt exist !"); + } + var role = await this.roleRepository.GetByIdAsync(accessControl.Role.Id); + if (role == null) + { + throw new ResourceNotFoundException($"The role {accessControl.Role.Name} with the id {accessControl.Role.Id} does'nt exist !"); + } + var acEntity = this.mapper.Map(accessControl); + await this.accessControlRepository.InsertAsync(acEntity); + await this.unitOfWork.SaveAsync(); + + var createdAc = await this.accessControlRepository.GetByIdAsync(acEntity.Id, ac => ac.Role); + var createdModel = this.mapper.Map(createdAc); + return createdModel; + + } + public async Task UpdateAccessControl(string id, AccessControlModel accessControl) + { + if (accessControl is null) + { + throw new ArgumentNullException(nameof(accessControl)); + } + var acEntity = await this.accessControlRepository.GetByIdAsync(id, ac => ac.Role); + if (acEntity is null) + { + throw new ResourceNotFoundException($"The AccessControl with the id {id} doesn't exist"); + } + var principal = await this.principalRepository.GetByIdAsync(accessControl.PrincipalId); + if (principal is null) + { + throw new ResourceNotFoundException($"The principal with the id {accessControl.PrincipalId} not found !"); + } + var role = await this.roleRepository.GetByIdAsync(accessControl.Role.Id); + if (role is null) + { + throw new ResourceNotFoundException($"Specified role with the id {accessControl.Role.Id} not found"); + } + acEntity.PrincipalId = accessControl.PrincipalId; + acEntity.RoleId = accessControl.Role.Id; + acEntity.Scope = accessControl.Scope; + accessControlRepository.Update(acEntity); + await this.unitOfWork.SaveAsync(); + + var createdAc = await this.accessControlRepository.GetByIdAsync(id, ac => ac.Role); + return this.mapper.Map(createdAc); + } + + public async Task DeleteAccessControl(string id) + { + var acEntity = await this.accessControlRepository.GetByIdAsync(id); + if (acEntity is null) + { + throw new ResourceNotFoundException($"The AccessControl with the id {id} doesn't exist"); + } + accessControlRepository.Delete(id); + await this.unitOfWork.SaveAsync(); + return true; + } + } +} diff --git a/src/IoTHub.Portal.Application/Services/GroupService.cs b/src/IoTHub.Portal.Application/Services/GroupService.cs new file mode 100644 index 000000000..34cdd211e --- /dev/null +++ b/src/IoTHub.Portal.Application/Services/GroupService.cs @@ -0,0 +1,173 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Services +{ + using AutoMapper; + using IoTHub.Portal.Crosscutting; + using IoTHub.Portal.Domain; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Exceptions; + using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Shared.Models.v10; + using IoTHub.Portal.Shared.Models.v10.Filters; + using System.Threading.Tasks; + + public class GroupService : IGroupManagementService + { + private readonly IGroupRepository groupRepository; + private readonly IMapper mapper; + private readonly IUnitOfWork unitOfWork; + private readonly IPrincipalRepository principalRepository; + private readonly IUserRepository userRepository; + private readonly IAccessControlRepository accessControlRepository; + public GroupService(IGroupRepository groupRepository, IMapper mapper, IUnitOfWork unitOfWork, + IPrincipalRepository principalRepository, IUserRepository userRepository, IAccessControlRepository accessControlRepository) + { + this.groupRepository = groupRepository; + this.mapper = mapper; + this.unitOfWork = unitOfWork; + this.principalRepository = principalRepository; + this.userRepository = userRepository; + this.accessControlRepository = accessControlRepository; + } + public async Task GetGroupDetailsAsync(string id) + { + var groupEntity = await groupRepository.GetByIdAsync(id, g => g.Members); + if (groupEntity is null) + { + throw new ResourceNotFoundException($"The group with the id {id} doesn't exist"); + } + var groupModel = mapper.Map(groupEntity); + return groupModel; + } + + async Task> IGroupManagementService.GetGroupPage( + string? searchKeyword, + int pageSize, + int pageNumber, + string[] orderBy) + { + var groupFilter = new GroupFilter + { + Keyword = searchKeyword, + PageSize = pageSize, + PageNumber = pageNumber, + OrderBy = orderBy + }; + + var groupPredicate = PredicateBuilder.True(); + if (!string.IsNullOrWhiteSpace(groupFilter.Keyword)) + { + groupPredicate = groupPredicate.And(grp => grp.Name.ToLower().Contains(groupFilter.Keyword.ToLower()) + || grp.Description.ToLower().Contains(groupFilter.Keyword.ToLower()) + ); + } + var paginatedGroup = await this.groupRepository.GetPaginatedListAsync(pageNumber, pageSize, orderBy, groupPredicate); + var paginatedGroupDto = new PaginatedResult + { + Data = paginatedGroup.Data.Select(x => this.mapper.Map(x)).ToList(), + TotalCount = paginatedGroup.TotalCount, + CurrentPage = paginatedGroup.CurrentPage, + PageSize = pageSize, + }; + return new PaginatedResult(paginatedGroupDto.Data, paginatedGroupDto.TotalCount); + } + + public async Task CreateGroupAsync(GroupDetailsModel group) + { + if (group is null) + { + throw new ArgumentNullException(nameof(group)); + } + var existingName = await this.groupRepository.GetByNameAsync(group.Name); + if (existingName is not null) + { + throw new ResourceAlreadyExistsException($"The Group with the name {group.Name} already exist !"); + } + var groupEntity = this.mapper.Map(group); + await groupRepository.InsertAsync(groupEntity); + await unitOfWork.SaveAsync(); + + var createdGroup = await this.groupRepository.GetByIdAsync(groupEntity.Id); + return this.mapper.Map(createdGroup); + } + + public async Task DeleteGroup(string id) + { + var group = await groupRepository.GetByIdAsync(id); + if (group is null) + { + throw new ResourceNotFoundException($"The Group with the id {id} that you want to delete doesn't exist !"); + } + principalRepository.Delete(group.PrincipalId); + groupRepository.Delete(id); + await unitOfWork.SaveAsync(); + return true; + } + + public async Task UpdateGroup(string id, GroupDetailsModel group) + { + if (group is null) + { + throw new ArgumentNullException(nameof(group)); + } + var groupEntity = await this.groupRepository.GetByIdAsync(id); + if (groupEntity is null) throw new ResourceNotFoundException($"The group with id {id} does'nt exist"); + var existingName = await this.groupRepository.GetByNameAsync(group.Name); + if (existingName is not null) + { + throw new ResourceAlreadyExistsException($"The Group tis the name {group.Name} already exist !"); + } + groupEntity.Name = group.Name; + groupEntity.Color = group.Color; + groupEntity.Description = group.Description; + this.groupRepository.Update(groupEntity); + await this.unitOfWork.SaveAsync(); + var updatedGroup = await this.groupRepository.GetByIdAsync(id); + return this.mapper.Map(updatedGroup); + } + + public async Task AddUserToGroup(string groupId, string userId) + { + var userEntity = await this.userRepository.GetByIdAsync(userId); + if (userEntity is null) + { + throw new ResourceNotFoundException($"The User with the id {userId} does'nt exist !"); + } + var groupEntity = await this.groupRepository.GetByIdAsync(groupId, g => g.Members); + if (groupEntity is null) + { + throw new ResourceNotFoundException($"The group with the id {groupId} does'nt exist !"); + } + var existingMember = groupEntity.Members.FirstOrDefault(u => u.Id == userId); + if (existingMember is null) + { + groupEntity.Members.Add(userEntity); + await unitOfWork.SaveAsync(); + return true; + } + else + { + throw new ResourceAlreadyExistsException($"The user with the id {userId} is already a member of this group !"); + } + } + + public async Task RemoveUserFromGroup(string groupId, string userId) + { + var groupEntity = await this.groupRepository.GetByIdAsync(groupId, g => g.Members); + if (groupEntity is null) + { + throw new ResourceNotFoundException($"The group with the id {groupId} does'nt exist !"); + } + var userEntity = groupEntity.Members.FirstOrDefault(userEntity => userEntity.Id == userId); + if (userEntity is null) + { + throw new ResourceNotFoundException($"The User with the id {userId} is not a member of the group !"); + } + _ = groupEntity.Members.Remove(userEntity); + await unitOfWork.SaveAsync(); + return true; + } + } +} diff --git a/src/IoTHub.Portal.Application/Services/IAccessControlManagementService.cs b/src/IoTHub.Portal.Application/Services/IAccessControlManagementService.cs new file mode 100644 index 000000000..3c347f1fd --- /dev/null +++ b/src/IoTHub.Portal.Application/Services/IAccessControlManagementService.cs @@ -0,0 +1,23 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Services +{ + using IoTHub.Portal.Shared.Models.v10; + + public interface IAccessControlManagementService + { + Task> GetAccessControlPage( + string? searchKeyword = null, + int pageSize = 10, + int pageNumber = 0, + string[] orderBy = null, + string? principalId = null + ); + Task GetAccessControlAsync(string Id); + + Task CreateAccessControl(AccessControlModel role); + Task UpdateAccessControl(string id, AccessControlModel accessControl); + Task DeleteAccessControl(string id); + } +} diff --git a/src/IoTHub.Portal.Application/Services/IDeviceModelService.cs b/src/IoTHub.Portal.Application/Services/IDeviceModelService.cs index 522e00a08..251257fc8 100644 --- a/src/IoTHub.Portal.Application/Services/IDeviceModelService.cs +++ b/src/IoTHub.Portal.Application/Services/IDeviceModelService.cs @@ -6,7 +6,7 @@ namespace IoTHub.Portal.Application.Services using System.Threading.Tasks; using IoTHub.Portal.Models.v10; using IoTHub.Portal.Shared.Models; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using Microsoft.AspNetCore.Http; diff --git a/src/IoTHub.Portal.Application/Services/IDeviceService.cs b/src/IoTHub.Portal.Application/Services/IDeviceService.cs index 63af1d0ea..0e7ea3778 100644 --- a/src/IoTHub.Portal.Application/Services/IDeviceService.cs +++ b/src/IoTHub.Portal.Application/Services/IDeviceService.cs @@ -9,7 +9,7 @@ namespace IoTHub.Portal.Application.Services using IoTHub.Portal.Shared.Models.v10; using Models.v10; using Shared.Models; - using Shared.Models.v1._0; + using Shared.Models.v10; public interface IDeviceService where TDto : IDeviceDetails diff --git a/src/IoTHub.Portal.Application/Services/IEdgeDevicesService.cs b/src/IoTHub.Portal.Application/Services/IEdgeDevicesService.cs index 772b88f91..5360531a1 100644 --- a/src/IoTHub.Portal.Application/Services/IEdgeDevicesService.cs +++ b/src/IoTHub.Portal.Application/Services/IEdgeDevicesService.cs @@ -6,7 +6,7 @@ namespace IoTHub.Portal.Application.Services using System.Collections.Generic; using System.Threading.Tasks; using IoTHub.Portal.Models.v10; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10; public interface IEdgeDevicesService diff --git a/src/IoTHub.Portal.Application/Services/IGroupManagementService.cs b/src/IoTHub.Portal.Application/Services/IGroupManagementService.cs new file mode 100644 index 000000000..a5283b102 --- /dev/null +++ b/src/IoTHub.Portal.Application/Services/IGroupManagementService.cs @@ -0,0 +1,23 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Services +{ + using IoTHub.Portal.Shared.Models.v10; + + public interface IGroupManagementService + { + Task> GetGroupPage( + string? searchKeyword = null, + int pageSize = 10, + int pageNumber = 0, + string[] orderBy = null + ); + Task GetGroupDetailsAsync(string groupId); + Task CreateGroupAsync(GroupDetailsModel groupCreateModel); + Task DeleteGroup(string userId); + Task UpdateGroup(string id, GroupDetailsModel group); + Task AddUserToGroup(string groupId, string userId); + Task RemoveUserFromGroup(string groupId, string userId); + } +} diff --git a/src/IoTHub.Portal.Application/Services/IIdeaService.cs b/src/IoTHub.Portal.Application/Services/IIdeaService.cs index 88f89fe6c..fd789ef90 100644 --- a/src/IoTHub.Portal.Application/Services/IIdeaService.cs +++ b/src/IoTHub.Portal.Application/Services/IIdeaService.cs @@ -4,7 +4,7 @@ namespace IoTHub.Portal.Application.Services { using System.Threading.Tasks; - using Shared.Models.v1._0; + using Shared.Models.v10; public interface IIdeaService { diff --git a/src/IoTHub.Portal.Application/Services/ILoRaWANConcentratorService.cs b/src/IoTHub.Portal.Application/Services/ILoRaWANConcentratorService.cs index 7dd63bb0b..07f372237 100644 --- a/src/IoTHub.Portal.Application/Services/ILoRaWANConcentratorService.cs +++ b/src/IoTHub.Portal.Application/Services/ILoRaWANConcentratorService.cs @@ -5,7 +5,7 @@ namespace IoTHub.Portal.Application.Services { using System.Threading.Tasks; using IoTHub.Portal.Models.v10.LoRaWAN; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; public interface ILoRaWANConcentratorService diff --git a/src/IoTHub.Portal.Application/Services/IRoleManagementService.cs b/src/IoTHub.Portal.Application/Services/IRoleManagementService.cs new file mode 100644 index 000000000..01f0a11ff --- /dev/null +++ b/src/IoTHub.Portal.Application/Services/IRoleManagementService.cs @@ -0,0 +1,21 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Services +{ + using IoTHub.Portal.Shared.Models.v10; + + public interface IRoleManagementService + { + Task> GetRolePage( + string? searchKeyword = null, + int pageSize = 10, + int pageNumber = 0, + string[] orderBy = null + ); + Task GetRoleDetailsAsync(string id); + Task CreateRole(RoleDetailsModel role); + Task UpdateRole(string id, RoleDetailsModel role); + Task DeleteRole(string id); + } +} diff --git a/src/IoTHub.Portal.Application/Services/IUserManagementService.cs b/src/IoTHub.Portal.Application/Services/IUserManagementService.cs new file mode 100644 index 000000000..2e9c0b5fe --- /dev/null +++ b/src/IoTHub.Portal.Application/Services/IUserManagementService.cs @@ -0,0 +1,24 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Services +{ + using IoTHub.Portal.Shared.Models.v10; + using IoTHub.Portal.Shared.Models.v10; + + public interface IUserManagementService + { + Task> GetUserPage( + string? searchName = null, + string? searchEmail = null, + int pageSize = 10, + int pageNumber = 0, + string[] orderBy = null + ); + Task GetUserDetailsAsync(string userId); + Task CreateUserAsync(UserDetailsModel userCreateModel); + Task UpdateUser(string id, UserDetailsModel user); + Task DeleteUser(string userId); + + } +} diff --git a/src/IoTHub.Portal.Application/Services/RoleService.cs b/src/IoTHub.Portal.Application/Services/RoleService.cs new file mode 100644 index 000000000..efc698827 --- /dev/null +++ b/src/IoTHub.Portal.Application/Services/RoleService.cs @@ -0,0 +1,177 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Threading.Tasks; + using AutoMapper; + using IoTHub.Portal.Domain; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Shared.Models.v10; + using IoTHub.Portal.Shared.Models.v10.Filters; + using Action = Domain.Entities.Action; + using IoTHub.Portal.Domain.Exceptions; + using IoTHub.Portal.Crosscutting; + + internal class RoleService : IRoleManagementService + { + private readonly IRoleRepository roleRepository; + private readonly IUnitOfWork unitOfWork; + private readonly IMapper mapper; + private readonly IActionRepository actionRepository; + + public RoleService(IRoleRepository roleRepository, IUnitOfWork unitOfWork, IMapper mapper, IActionRepository actionRepository) + { + this.roleRepository = roleRepository; + this.unitOfWork = unitOfWork; + this.mapper = mapper; + this.actionRepository = actionRepository; + } + + + public async Task DeleteRole(string id) + { + var actionsToRemove = new List(); + var role = await this.roleRepository.GetByIdAsync(id, r => r.Actions); + if (role == null) + { + throw new ResourceNotFoundException($"The role with name {id} doesn't exist"); + } + foreach (var action in role.Actions) + { + actionsToRemove.Add(action.Id); + } + foreach (var action in actionsToRemove) + { + this.actionRepository.Delete(action); + } + roleRepository.Delete(role.Id); + await unitOfWork.SaveAsync(); + return true; + } + + public async Task> GetRolePage( + string? searchKeyword = null, + int pageSize = 10, + int pageNumber = 0, + string[] orderBy = null) + { + var roleFilter = new RoleFilter + { + Keyword = searchKeyword, + PageSize = pageSize, + PageNumber = pageNumber, + OrderBy = orderBy + }; + + var rolePredicate = PredicateBuilder.True(); + if (!string.IsNullOrWhiteSpace(roleFilter.Keyword)) + { + rolePredicate = rolePredicate.And(role => role.Name.ToLower().Contains(roleFilter.Keyword.ToLower()) || + role.Description.ToLower().Contains(roleFilter.Keyword.ToLower())); + } + + var paginatedRole = await this.roleRepository.GetPaginatedListAsync(pageNumber, + pageSize, + orderBy, + rolePredicate, + includes: new Expression>[] { role => role.Actions}); + + var paginatedRoleDto = new PaginatedResult + { + Data = paginatedRole.Data.Select(x => this.mapper.Map(x)).ToList(), + TotalCount = paginatedRole.TotalCount, + CurrentPage = paginatedRole.CurrentPage, + PageSize = pageSize + }; + + return new PaginatedResult(paginatedRoleDto.Data, paginatedRoleDto.TotalCount); + + } + + public async Task GetRoleDetailsAsync(string id) + { + var roleEntity = await this.roleRepository.GetByIdAsync(id, r => r.Actions); + if (roleEntity is null) + { + throw new ResourceNotFoundException($"The role with name {id} doesn't exist"); + } + var roleModel = this.mapper.Map(roleEntity); + return roleModel; + } + + public async Task CreateRole(RoleDetailsModel role) + { + if (role is null) + { + throw new ArgumentNullException(nameof(role)); + } + + var roleWithName = await this.roleRepository.GetByNameAsync(role.Name); + if (roleWithName is not null) + { + throw new ResourceAlreadyExistsException($"The role with the name {role.Name} already exist, a role name should be unique !"); + } + + var roleEntity = this.mapper.Map(role); + await this.roleRepository.InsertAsync(roleEntity); + await this.unitOfWork.SaveAsync(); + + + var createdRole = await this.roleRepository.GetByIdAsync(roleEntity.Id); + var createdRoleDto = this.mapper.Map(createdRole); + + return createdRoleDto; + } + + public async Task UpdateRole(string id, RoleDetailsModel role) + { + if (role is null) + { + throw new ArgumentNullException(nameof(role)); + } + + var roleEntity = await this.roleRepository.GetByIdAsync(id, r => r.Actions); + if (roleEntity is null) + { + throw new ResourceNotFoundException($"The role with id {id} doesn't exist"); + } + var roleWithName = await this.roleRepository.GetByNameAsync(role.Name); + if (roleWithName is not null) + { + throw new ResourceAlreadyExistsException($"The role with the name {role.Name} already exist, a role name should be unique !"); + } + roleEntity.Name = role.Name; + roleEntity.Description = role.Description; + + var actionsToRemove = roleEntity.Actions.Where(a => !role.Actions.Any(na => na == a.Name)).ToList(); + foreach (var action in actionsToRemove) + { + _ = roleEntity.Actions.Remove(action); + this.actionRepository.Delete(action.Id); + } + + foreach (var actionName in role.Actions) + { + var action = roleEntity.Actions.FirstOrDefault(a => a.Name == actionName); + if (action == null) + { + roleEntity.Actions.Add(new Action { Name = actionName }); + } + } + + this.roleRepository.Update(roleEntity); + await this.unitOfWork.SaveAsync(); + + var updatedRole = await this.roleRepository.GetByIdAsync(roleEntity.Id); + var updatedRoleDto = this.mapper.Map(updatedRole); + + return updatedRoleDto; + } + } +} diff --git a/src/IoTHub.Portal.Application/Services/UserService.cs b/src/IoTHub.Portal.Application/Services/UserService.cs new file mode 100644 index 000000000..0fb95bde1 --- /dev/null +++ b/src/IoTHub.Portal.Application/Services/UserService.cs @@ -0,0 +1,137 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Application.Services +{ + using AutoMapper; + using IoTHub.Portal.Crosscutting; + using IoTHub.Portal.Domain; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Exceptions; + using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Shared.Models.v10; + using IoTHub.Portal.Shared.Models.v10.Filters; + using System.Threading.Tasks; + + public class UserService : IUserManagementService + { + private readonly IUserRepository userRepository; + private readonly IMapper mapper; + private readonly IUnitOfWork unitOfWork; + private readonly IPrincipalRepository principalRepository; + + public UserService(IUserRepository userRepository, IMapper mapper, IUnitOfWork unitOfWork, IPrincipalRepository principalRepository) + { + this.userRepository = userRepository; + this.mapper = mapper; + this.unitOfWork = unitOfWork; + this.principalRepository = principalRepository; + } + public async Task GetUserDetailsAsync(string id) + { + var user = await userRepository.GetByIdAsync(id, u => u.Groups); + if (user == null) throw new ResourceNotFoundException($"The user with the id {id} doesn't exist"); + return mapper.Map(user); + } + + public async Task CreateUserAsync(UserDetailsModel user) + { + if (user is null) + { + throw new ArgumentNullException(nameof(user)); + } + var existingName = await this.userRepository.GetByNameAsync(user.Name); + if (existingName is not null) + { + throw new ResourceAlreadyExistsException($"The User tis the name {user.Name} already exist !"); + } + var userEntity = this.mapper.Map(user); + await userRepository.InsertAsync(userEntity); + + await unitOfWork.SaveAsync(); + + var createdEntity = await this.userRepository.GetByIdAsync(userEntity.Id); + var createdModel = this.mapper.Map(createdEntity); + return createdModel; + } + + public async Task> GetUserPage( + string? searchName, + string? searchEmail, + int pageSize, + int pageNumber, + string[] orderBy + ) + { + var userFilter = new UserFilter + { + SearchName = searchName, + SearchEmail = searchEmail, + PageSize = pageSize, + PageNumber = pageNumber, + OrderBy = orderBy + }; + + var userPredicate = PredicateBuilder.True(); + if (!string.IsNullOrWhiteSpace(userFilter.SearchName)) + { + userPredicate = userPredicate.And(user => user.Name.ToLower().Contains(userFilter.SearchName.ToLower()) + || user.FamilyName.ToLower().Contains(userFilter.SearchName.ToLower()) || + user.GivenName.ToLower().Contains(userFilter.SearchName.ToLower()) + ); + } + var paginatedUser = await this.userRepository.GetPaginatedListAsync(pageNumber, pageSize, orderBy, userPredicate); + var paginatedUserDto = new PaginatedResult + { + Data = paginatedUser.Data.Select(x => this.mapper.Map(x)).ToList(), + TotalCount = paginatedUser.TotalCount, + CurrentPage = paginatedUser.CurrentPage, + PageSize = pageSize + }; + + return new PaginatedResult(paginatedUserDto.Data, paginatedUserDto.TotalCount); + } + + public async Task UpdateUser(string id, UserDetailsModel user) + { + if (user is null) + { + throw new ArgumentNullException(nameof(user)); + } + var userEntity = await this.userRepository.GetByIdAsync(id); + if (userEntity == null) throw new ResourceNotFoundException($"The User with the id {id} does'nt exist !"); + var existingName = await this.userRepository.GetByNameAsync(user.Name); + if (existingName is not null) + { + throw new ResourceAlreadyExistsException($"The User with the name {user.Name} already exist !"); + } + + userEntity.Email = user.Email; + userEntity.GivenName = user.GivenName; + userEntity.Name = user.Name; + userEntity.FamilyName = user.FamilyName; + userEntity.Avatar = user.Avatar; + + this.userRepository.Update(userEntity); + await this.unitOfWork.SaveAsync(); + var createdUser = await this.userRepository.GetByIdAsync(id); + var toModel = this.mapper.Map(createdUser); + return toModel; + + } + + public async Task DeleteUser(string id) + { + var user = await userRepository.GetByIdAsync(id); + if (user is null) + { + throw new ResourceNotFoundException($"The User with the id {id} that you want to delete doesn't exist !"); + } + + principalRepository.Delete(user.PrincipalId); + userRepository.Delete(id); + await unitOfWork.SaveAsync(); + return true; + } + } +} diff --git a/src/IoTHub.Portal.Client/Components/Dashboard/DashboardMetrics.razor b/src/IoTHub.Portal.Client/Components/Dashboard/DashboardMetrics.razor index f49121c63..14a4723d2 100644 --- a/src/IoTHub.Portal.Client/Components/Dashboard/DashboardMetrics.razor +++ b/src/IoTHub.Portal.Client/Components/Dashboard/DashboardMetrics.razor @@ -1,4 +1,4 @@ -@using IoTHub.Portal.Shared.Models.v1._0 +@using IoTHub.Portal.Shared.Models.v10 @using IoTHub.Portal.Models.v10 @using IoTHub.Portal.Shared.Constants diff --git a/src/IoTHub.Portal.Client/Dialogs/Ideas/SubmitIdeaDialog.razor b/src/IoTHub.Portal.Client/Dialogs/Ideas/SubmitIdeaDialog.razor index 87c9734ae..dca4ded1f 100644 --- a/src/IoTHub.Portal.Client/Dialogs/Ideas/SubmitIdeaDialog.razor +++ b/src/IoTHub.Portal.Client/Dialogs/Ideas/SubmitIdeaDialog.razor @@ -1,4 +1,4 @@ -@using IoTHub.Portal.Shared.Models.v1._0 +@using IoTHub.Portal.Shared.Models.v10 @inject ISnackbar SnackBar @inject IJSRuntime JsRuntime diff --git a/src/IoTHub.Portal.Client/Handlers/ProblemDetailsHandler.cs b/src/IoTHub.Portal.Client/Handlers/ProblemDetailsHandler.cs index c831fbb0e..55cac01c6 100644 --- a/src/IoTHub.Portal.Client/Handlers/ProblemDetailsHandler.cs +++ b/src/IoTHub.Portal.Client/Handlers/ProblemDetailsHandler.cs @@ -1,19 +1,19 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace IoTHub.Portal.Client.Handlers +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Client.Handlers { using System.Net; - using System.Net.Http; - using System.Net.Http.Json; - using System.Threading.Tasks; + using System.Net.Http; + using System.Net.Http.Json; + using System.Threading.Tasks; using Exceptions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Models; using MudBlazor; - public class ProblemDetailsHandler : DelegatingHandler + public class ProblemDetailsHandler : DelegatingHandler { private readonly ISnackbar snackbar; private readonly NavigationManager navigationManager; @@ -23,16 +23,15 @@ public ProblemDetailsHandler(ISnackbar snackbar, NavigationManager navigationMan this.snackbar = snackbar; this.navigationManager = navigationManager; } - - protected override async Task SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) - { - var response = await base.SendAsync(request, cancellationToken); - - if (response.IsSuccessStatusCode) - { - return response; - } + protected override async Task SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) + { + var response = await base.SendAsync(request, cancellationToken); + + if (response.IsSuccessStatusCode) + { + return response; + } else if (response.StatusCode.Equals(HttpStatusCode.Unauthorized)) { _ = this.snackbar.Add("You are not authorized", Severity.Error, config => @@ -49,11 +48,11 @@ protected override async Task SendAsync(HttpRequestMessage }; }); - } - - var problemDetails = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); - - throw new ProblemDetailsException(problemDetails); - } - } -} + } + + var problemDetails = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); + + throw new ProblemDetailsException(problemDetails); + } + } +} diff --git a/src/IoTHub.Portal.Client/Pages/EdgeDevices/CreateEdgeDevicePage.razor b/src/IoTHub.Portal.Client/Pages/EdgeDevices/CreateEdgeDevicePage.razor index 41fd8f786..b08816eca 100644 --- a/src/IoTHub.Portal.Client/Pages/EdgeDevices/CreateEdgeDevicePage.razor +++ b/src/IoTHub.Portal.Client/Pages/EdgeDevices/CreateEdgeDevicePage.razor @@ -7,7 +7,6 @@ @attribute [Authorize] @inject PortalSettings Portal @inject ISnackbar Snackbar -@inject PortalSettings Portal @inject NavigationManager NavigationManager @inject IEdgeDeviceClientService EdgeDeviceClientService @inject IEdgeModelClientService EdgeModelClientService @@ -133,7 +132,7 @@ } - + diff --git a/src/IoTHub.Portal.Client/Services/DashboardMetricsClientService.cs b/src/IoTHub.Portal.Client/Services/DashboardMetricsClientService.cs index 429d142ba..8a220f521 100644 --- a/src/IoTHub.Portal.Client/Services/DashboardMetricsClientService.cs +++ b/src/IoTHub.Portal.Client/Services/DashboardMetricsClientService.cs @@ -6,7 +6,7 @@ namespace IoTHub.Portal.Client.Services using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; - using Portal.Shared.Models.v1._0; + using Portal.Shared.Models.v10; public class DashboardMetricsClientService : IDashboardMetricsClientService { diff --git a/src/IoTHub.Portal.Client/Services/IDashboardMetricsClientService.cs b/src/IoTHub.Portal.Client/Services/IDashboardMetricsClientService.cs index bdb27e947..aed14ca5d 100644 --- a/src/IoTHub.Portal.Client/Services/IDashboardMetricsClientService.cs +++ b/src/IoTHub.Portal.Client/Services/IDashboardMetricsClientService.cs @@ -4,7 +4,7 @@ namespace IoTHub.Portal.Client.Services { using System.Threading.Tasks; - using Portal.Shared.Models.v1._0; + using Portal.Shared.Models.v10; public interface IDashboardMetricsClientService { diff --git a/src/IoTHub.Portal.Client/Services/IIdeaClientService.cs b/src/IoTHub.Portal.Client/Services/IIdeaClientService.cs index 9ee76e22d..1f0cc4c25 100644 --- a/src/IoTHub.Portal.Client/Services/IIdeaClientService.cs +++ b/src/IoTHub.Portal.Client/Services/IIdeaClientService.cs @@ -4,7 +4,7 @@ namespace IoTHub.Portal.Client.Services { using System.Threading.Tasks; - using Portal.Shared.Models.v1._0; + using Portal.Shared.Models.v10; public interface IIdeaClientService { diff --git a/src/IoTHub.Portal.Client/Services/ILoRaWanDeviceClientService.cs b/src/IoTHub.Portal.Client/Services/ILoRaWanDeviceClientService.cs index c0901426c..9fbbfc38b 100644 --- a/src/IoTHub.Portal.Client/Services/ILoRaWanDeviceClientService.cs +++ b/src/IoTHub.Portal.Client/Services/ILoRaWanDeviceClientService.cs @@ -6,7 +6,7 @@ namespace IoTHub.Portal.Client.Services using System.Collections.Generic; using System.Threading.Tasks; using IoTHub.Portal.Models.v10.LoRaWAN; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10; public interface ILoRaWanDeviceClientService diff --git a/src/IoTHub.Portal.Client/Services/IdeaClientService.cs b/src/IoTHub.Portal.Client/Services/IdeaClientService.cs index 473b61d4b..21dc7f321 100644 --- a/src/IoTHub.Portal.Client/Services/IdeaClientService.cs +++ b/src/IoTHub.Portal.Client/Services/IdeaClientService.cs @@ -6,7 +6,7 @@ namespace IoTHub.Portal.Client.Services using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; - using Portal.Shared.Models.v1._0; + using Portal.Shared.Models.v10; public class IdeaClientService : IIdeaClientService { diff --git a/src/IoTHub.Portal.Client/Services/LoRaWanDeviceClientService.cs b/src/IoTHub.Portal.Client/Services/LoRaWanDeviceClientService.cs index 0cec22e43..e91cff881 100644 --- a/src/IoTHub.Portal.Client/Services/LoRaWanDeviceClientService.cs +++ b/src/IoTHub.Portal.Client/Services/LoRaWanDeviceClientService.cs @@ -7,7 +7,7 @@ namespace IoTHub.Portal.Client.Services using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10; using Portal.Models.v10.LoRaWAN; diff --git a/src/IoTHub.Portal.Infrastructure/Repositories/PredicateBuilder.cs b/src/IoTHub.Portal.Crosscutting/PredicateBuilder.cs similarity index 93% rename from src/IoTHub.Portal.Infrastructure/Repositories/PredicateBuilder.cs rename to src/IoTHub.Portal.Crosscutting/PredicateBuilder.cs index a0779413e..86c56b970 100644 --- a/src/IoTHub.Portal.Infrastructure/Repositories/PredicateBuilder.cs +++ b/src/IoTHub.Portal.Crosscutting/PredicateBuilder.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Infrastructure.Repositories +namespace IoTHub.Portal.Crosscutting { using System.Linq.Expressions; diff --git a/src/IoTHub.Portal.Domain/Entities/AccessControl.cs b/src/IoTHub.Portal.Domain/Entities/AccessControl.cs index c377ce969..b17753ae3 100644 --- a/src/IoTHub.Portal.Domain/Entities/AccessControl.cs +++ b/src/IoTHub.Portal.Domain/Entities/AccessControl.cs @@ -9,6 +9,8 @@ public class AccessControl : EntityBase { public string Scope { get; set; } = default!; public string RoleId { get; set; } = default!; - public Role Role { get; set; } = new Role(); + public virtual Role Role { get; set; } + public string PrincipalId { get; set; } + public virtual Principal Principal { get; set; } } } diff --git a/src/IoTHub.Portal.Domain/Entities/Action.cs b/src/IoTHub.Portal.Domain/Entities/Action.cs index 205bd4a65..6f622a9d8 100644 --- a/src/IoTHub.Portal.Domain/Entities/Action.cs +++ b/src/IoTHub.Portal.Domain/Entities/Action.cs @@ -3,10 +3,12 @@ namespace IoTHub.Portal.Domain.Entities { + using System.ComponentModel.DataAnnotations; using IoTHub.Portal.Domain.Base; public class Action : EntityBase { + [Required] public string Name { get; set; } = default!; } } diff --git a/src/IoTHub.Portal.Domain/Entities/Group.cs b/src/IoTHub.Portal.Domain/Entities/Group.cs index 44d7d2f7e..126712cfa 100644 --- a/src/IoTHub.Portal.Domain/Entities/Group.cs +++ b/src/IoTHub.Portal.Domain/Entities/Group.cs @@ -7,13 +7,17 @@ namespace IoTHub.Portal.Domain.Entities using IoTHub.Portal.Domain.Base; using System.Collections.Generic; using System.Collections.ObjectModel; + using System.ComponentModel.DataAnnotations; public class Group : EntityBase { + [Required] public string Name { get; set; } = default!; - public string Avatar { get; set; } = default!; - public virtual ICollection Members { get; set; } = new Collection(); - public virtual ICollection AccessControls { get; set; } = new Collection(); + public string Color { get; set; } = default!; + public string? Description { get; set; } = default!; + public virtual ICollection Members { get; set; } = new Collection(); + public string PrincipalId { get; set; } + public virtual Principal Principal { get; set; } = new Principal(); } } diff --git a/src/IoTHub.Portal.Domain/Entities/UserMemberShip.cs b/src/IoTHub.Portal.Domain/Entities/Principal.cs similarity index 53% rename from src/IoTHub.Portal.Domain/Entities/UserMemberShip.cs rename to src/IoTHub.Portal.Domain/Entities/Principal.cs index 56d30bbf8..0c09abb98 100644 --- a/src/IoTHub.Portal.Domain/Entities/UserMemberShip.cs +++ b/src/IoTHub.Portal.Domain/Entities/Principal.cs @@ -1,16 +1,19 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. + namespace IoTHub.Portal.Domain.Entities { using IoTHub.Portal.Domain.Base; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.ComponentModel.DataAnnotations; - public class UserMemberShip : EntityBase + public class Principal : EntityBase { - public string UserId { get; set; } = default!; + [Required] + public virtual ICollection AccessControls { get; set; } = new Collection(); public virtual User? User { get; set; } - - public string GroupId { get; set; } = default!; public virtual Group? Group { get; set; } } } diff --git a/src/IoTHub.Portal.Domain/Entities/Role.cs b/src/IoTHub.Portal.Domain/Entities/Role.cs index 787fe50ce..7ef3b9ab4 100644 --- a/src/IoTHub.Portal.Domain/Entities/Role.cs +++ b/src/IoTHub.Portal.Domain/Entities/Role.cs @@ -5,11 +5,15 @@ namespace IoTHub.Portal.Domain.Entities { using System.Collections.Generic; using System.Collections.ObjectModel; + using System.ComponentModel.DataAnnotations; using IoTHub.Portal.Domain.Base; public class Role : EntityBase { + [Required] public string Name { get; set; } = default!; + public string Color { get; set; } = default!; + public string? Description { get; set; } = default!; public virtual ICollection Actions { get; set; } = new Collection(); } } diff --git a/src/IoTHub.Portal.Domain/Entities/User.cs b/src/IoTHub.Portal.Domain/Entities/User.cs index cf84d3d55..d7789bdbb 100644 --- a/src/IoTHub.Portal.Domain/Entities/User.cs +++ b/src/IoTHub.Portal.Domain/Entities/User.cs @@ -6,14 +6,20 @@ namespace IoTHub.Portal.Domain.Entities { using System.Collections.Generic; using System.Collections.ObjectModel; + using System.ComponentModel.DataAnnotations; using IoTHub.Portal.Domain.Base; public class User : EntityBase { + [Required] public string Email { get; set; } = default!; - public string Name { get; set; } = default!; - public string Forename { get; set; } = default!; - public virtual ICollection Groups { get; set; } = new Collection(); - public virtual ICollection AccessControls { get; set; } = new Collection(); + [Required] + public string GivenName { get; set; } = default!; + public string? Name { get; set; } = default!; + public string? FamilyName { get; set; } = default!; + public string? Avatar { get; set; } = default!; + public virtual ICollection Groups { get; set; } = new Collection(); + public string PrincipalId { get; set; } + public virtual Principal Principal { get; set; } = new Principal(); } } diff --git a/src/IoTHub.Portal.Domain/Entities/indexerAttribute.cs b/src/IoTHub.Portal.Domain/Entities/indexerAttribute.cs new file mode 100644 index 000000000..636a20931 --- /dev/null +++ b/src/IoTHub.Portal.Domain/Entities/indexerAttribute.cs @@ -0,0 +1,11 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Domain.Entities +{ + using System; + + internal class IndexerAttribute : Attribute + { + } +} diff --git a/src/IoTHub.Portal.Domain/IRepository.cs b/src/IoTHub.Portal.Domain/IRepository.cs index e41b23f12..b932063ce 100644 --- a/src/IoTHub.Portal.Domain/IRepository.cs +++ b/src/IoTHub.Portal.Domain/IRepository.cs @@ -7,7 +7,7 @@ namespace IoTHub.Portal.Domain using System.Linq.Expressions; using System.Threading.Tasks; using Base; - using Portal.Shared.Models.v1._0; + using Portal.Shared.Models.v10; public interface IRepository where T : EntityBase { diff --git a/src/IoTHub.Portal.Domain/Repositories/IAccessControlRepository.cs b/src/IoTHub.Portal.Domain/Repositories/IAccessControlRepository.cs new file mode 100644 index 000000000..0dd291f18 --- /dev/null +++ b/src/IoTHub.Portal.Domain/Repositories/IAccessControlRepository.cs @@ -0,0 +1,11 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Domain.Repositories +{ + using IoTHub.Portal.Domain.Entities; + + public interface IAccessControlRepository : IRepository + { + } +} diff --git a/src/IoTHub.Portal.Domain/Repositories/IActionRepository.cs b/src/IoTHub.Portal.Domain/Repositories/IActionRepository.cs new file mode 100644 index 000000000..8d4bbbeff --- /dev/null +++ b/src/IoTHub.Portal.Domain/Repositories/IActionRepository.cs @@ -0,0 +1,11 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Domain.Repositories +{ + using IoTHub.Portal.Domain.Entities; + + public interface IActionRepository : IRepository + { + } +} diff --git a/src/IoTHub.Portal.Domain/Repositories/IGroupRepository.cs b/src/IoTHub.Portal.Domain/Repositories/IGroupRepository.cs new file mode 100644 index 000000000..b43e98f56 --- /dev/null +++ b/src/IoTHub.Portal.Domain/Repositories/IGroupRepository.cs @@ -0,0 +1,13 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Domain.Repositories +{ + using System.Linq.Expressions; + using IoTHub.Portal.Domain.Entities; + + public interface IGroupRepository : IRepository + { + Task GetByNameAsync(string userName, params Expression>[] includeProperties); + } +} diff --git a/src/IoTHub.Portal.Domain/Repositories/IPrincipalRepository.cs b/src/IoTHub.Portal.Domain/Repositories/IPrincipalRepository.cs new file mode 100644 index 000000000..9524dd996 --- /dev/null +++ b/src/IoTHub.Portal.Domain/Repositories/IPrincipalRepository.cs @@ -0,0 +1,11 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Domain.Repositories +{ + using IoTHub.Portal.Domain.Entities; + + public interface IPrincipalRepository : IRepository + { + } +} diff --git a/src/IoTHub.Portal.Domain/Repositories/IRoleRepository.cs b/src/IoTHub.Portal.Domain/Repositories/IRoleRepository.cs new file mode 100644 index 000000000..03753b381 --- /dev/null +++ b/src/IoTHub.Portal.Domain/Repositories/IRoleRepository.cs @@ -0,0 +1,13 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Domain.Repositories +{ + using System.Linq.Expressions; + using IoTHub.Portal.Domain.Entities; + + public interface IRoleRepository : IRepository + { + Task GetByNameAsync(string roleName, params Expression>[] includeProperties); + } +} diff --git a/src/IoTHub.Portal.Domain/Repositories/IUserRepository.cs b/src/IoTHub.Portal.Domain/Repositories/IUserRepository.cs new file mode 100644 index 000000000..6caabdcdf --- /dev/null +++ b/src/IoTHub.Portal.Domain/Repositories/IUserRepository.cs @@ -0,0 +1,14 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Domain.Repositories +{ + using System.Linq.Expressions; + using IoTHub.Portal.Domain.Entities; + + public interface IUserRepository : IRepository + { + Task GetByNameAsync(string userName, params Expression>[] includeProperties); + } + +} diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/ConcentratorMetricExporterJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/ConcentratorMetricExporterJob.cs index 2b3c5d70a..95b170119 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/ConcentratorMetricExporterJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/ConcentratorMetricExporterJob.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Infrastructure.Jobs using Microsoft.Extensions.Logging; using Prometheus; using Quartz; - using Shared.Models.v1._0; + using Shared.Models.v10; [DisallowConcurrentExecution] public class ConcentratorMetricExporterJob : IJob diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/ConcentratorMetricLoaderJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/ConcentratorMetricLoaderJob.cs index 6331f4993..b36c120b8 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/ConcentratorMetricLoaderJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/ConcentratorMetricLoaderJob.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Infrastructure.Jobs using IoTHub.Portal.Application.Services; using Microsoft.Extensions.Logging; using Quartz; - using Shared.Models.v1._0; + using Shared.Models.v10; [DisallowConcurrentExecution] public class ConcentratorMetricLoaderJob : IJob diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/DeviceMetricExporterJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/DeviceMetricExporterJob.cs index 49b68f998..a0141dddb 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/DeviceMetricExporterJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/DeviceMetricExporterJob.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Infrastructure.Jobs using Microsoft.Extensions.Logging; using Prometheus; using Quartz; - using Shared.Models.v1._0; + using Shared.Models.v10; [DisallowConcurrentExecution] public class DeviceMetricExporterJob : IJob diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/DeviceMetricLoaderJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/DeviceMetricLoaderJob.cs index 6b69a6273..8f517ade9 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/DeviceMetricLoaderJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/DeviceMetricLoaderJob.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Infrastructure.Jobs using IoTHub.Portal.Application.Services; using Microsoft.Extensions.Logging; using Quartz; - using Shared.Models.v1._0; + using Shared.Models.v10; [DisallowConcurrentExecution] public class DeviceMetricLoaderJob : IJob diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/EdgeDeviceMetricExporterJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/EdgeDeviceMetricExporterJob.cs index 37bb752a5..0a1413d1a 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/EdgeDeviceMetricExporterJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/EdgeDeviceMetricExporterJob.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Infrastructure.Jobs using Microsoft.Extensions.Logging; using Prometheus; using Quartz; - using Shared.Models.v1._0; + using Shared.Models.v10; [DisallowConcurrentExecution] public class EdgeDeviceMetricExporterJob : IJob diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/EdgeDeviceMetricLoaderJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/EdgeDeviceMetricLoaderJob.cs index 70347a9b6..286b0cfc9 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/EdgeDeviceMetricLoaderJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/EdgeDeviceMetricLoaderJob.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Infrastructure.Jobs using IoTHub.Portal.Application.Services; using Microsoft.Extensions.Logging; using Quartz; - using Shared.Models.v1._0; + using Shared.Models.v10; [DisallowConcurrentExecution] public class EdgeDeviceMetricLoaderJob : IJob diff --git a/src/IoTHub.Portal.Infrastructure/Jobs/SyncGatewayIDJob.cs b/src/IoTHub.Portal.Infrastructure/Jobs/SyncGatewayIDJob.cs index 7e4f6dbe0..d5af11386 100644 --- a/src/IoTHub.Portal.Infrastructure/Jobs/SyncGatewayIDJob.cs +++ b/src/IoTHub.Portal.Infrastructure/Jobs/SyncGatewayIDJob.cs @@ -6,7 +6,7 @@ namespace IoTHub.Portal.Infrastructure.Jobs using System; using System.Threading.Tasks; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using Microsoft.Extensions.Logging; using Quartz; diff --git a/src/IoTHub.Portal.Infrastructure/PortalDbContext.cs b/src/IoTHub.Portal.Infrastructure/PortalDbContext.cs index 0fa85e8f4..bb95d7bea 100644 --- a/src/IoTHub.Portal.Infrastructure/PortalDbContext.cs +++ b/src/IoTHub.Portal.Infrastructure/PortalDbContext.cs @@ -29,6 +29,7 @@ public class PortalDbContext : DbContext, IDataProtectionKeyContext public DbSet AccessControls { get; set; } public DbSet Roles { get; set; } public DbSet Actions { get; set; } + public DbSet Principals { get; set; } public PortalDbContext(DbContextOptions options) : base(options) @@ -62,30 +63,28 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .WithMany() .HasForeignKey(x => x.DeviceModelId); - _ = modelBuilder.Entity() - .HasKey(m => new { m.UserId, m.GroupId }); - - _ = modelBuilder.Entity() - .HasOne(m => m.User) - .WithMany(u => u.Groups) - .HasForeignKey(m => m.UserId); + _ = modelBuilder.Entity() + .HasIndex(r => r.Name) + .IsUnique(); - _ = modelBuilder.Entity() - .HasOne(m => m.Group) - .WithMany(g => g.Members) - .HasForeignKey(m => m.GroupId); + _ = modelBuilder.Entity() + .HasOne(ac => ac.Role) + .WithMany() + .HasForeignKey(ac => ac.RoleId) + .OnDelete(DeleteBehavior.Restrict); - _ = modelBuilder.Entity() - .HasMany(a => a.AccessControls); + _ = modelBuilder.Entity() + .HasIndex(u => u.Email) + .IsUnique(); _ = modelBuilder.Entity() - .HasMany(a => a.AccessControls); + .HasIndex(u => u.GivenName) + .IsUnique(); - _ = modelBuilder.Entity() - .HasMany(a => a.Actions); + _ = modelBuilder.Entity() + .HasIndex(g => g.Name) + .IsUnique(); - _ = modelBuilder.Entity() - .HasOne(r => r.Role); _ = modelBuilder.Entity() .HasMany(c => c.Tags) diff --git a/src/IoTHub.Portal.Infrastructure/Repositories/AccessControlRepository.cs b/src/IoTHub.Portal.Infrastructure/Repositories/AccessControlRepository.cs new file mode 100644 index 000000000..d953c294d --- /dev/null +++ b/src/IoTHub.Portal.Infrastructure/Repositories/AccessControlRepository.cs @@ -0,0 +1,13 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Infrastructure.Repositories +{ + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Repositories; + public class AccessControlRepository : GenericRepository, IAccessControlRepository + { + public AccessControlRepository(PortalDbContext context) : base(context) { } + + } +} diff --git a/src/IoTHub.Portal.Infrastructure/Repositories/ActionRepository.cs b/src/IoTHub.Portal.Infrastructure/Repositories/ActionRepository.cs new file mode 100644 index 000000000..5205e19d5 --- /dev/null +++ b/src/IoTHub.Portal.Infrastructure/Repositories/ActionRepository.cs @@ -0,0 +1,15 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Infrastructure.Repositories +{ + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Repositories; + + public class ActionRepository : GenericRepository, IActionRepository + { + public ActionRepository(PortalDbContext context) : base(context) + { + } + } +} diff --git a/src/IoTHub.Portal.Infrastructure/Repositories/GenericRepository.cs b/src/IoTHub.Portal.Infrastructure/Repositories/GenericRepository.cs index 278954f62..ca3aeaa98 100644 --- a/src/IoTHub.Portal.Infrastructure/Repositories/GenericRepository.cs +++ b/src/IoTHub.Portal.Infrastructure/Repositories/GenericRepository.cs @@ -9,7 +9,7 @@ namespace IoTHub.Portal.Infrastructure.Repositories using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; - using Shared.Models.v1._0; + using Shared.Models.v10; using System.Linq.Dynamic.Core; public class GenericRepository : IRepository where T : EntityBase diff --git a/src/IoTHub.Portal.Infrastructure/Repositories/GroupRepository.cs b/src/IoTHub.Portal.Infrastructure/Repositories/GroupRepository.cs new file mode 100644 index 000000000..4a502c6a4 --- /dev/null +++ b/src/IoTHub.Portal.Infrastructure/Repositories/GroupRepository.cs @@ -0,0 +1,27 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Infrastructure.Repositories +{ + using System.Linq.Expressions; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Repositories; + using Microsoft.EntityFrameworkCore; + + public class GroupRepository : GenericRepository, IGroupRepository + { + public GroupRepository(PortalDbContext context) : base(context) + { + } + + public async Task GetByNameAsync(string groupName, params Expression>[] includeProperties) + { + IQueryable query = context.Groups; + foreach (var includeProperty in includeProperties) + { + query = query.Include(includeProperty); + } + return await query.FirstOrDefaultAsync(g => g.Name == groupName); + } + } +} diff --git a/src/IoTHub.Portal.Infrastructure/Repositories/PrincipalRepository.cs b/src/IoTHub.Portal.Infrastructure/Repositories/PrincipalRepository.cs new file mode 100644 index 000000000..a85272f4d --- /dev/null +++ b/src/IoTHub.Portal.Infrastructure/Repositories/PrincipalRepository.cs @@ -0,0 +1,15 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Infrastructure.Repositories +{ + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Repositories; + + public class PrincipalRepository : GenericRepository, IPrincipalRepository + { + public PrincipalRepository(PortalDbContext context) : base(context) + { + } + } +} diff --git a/src/IoTHub.Portal.Infrastructure/Repositories/RoleRepository.cs b/src/IoTHub.Portal.Infrastructure/Repositories/RoleRepository.cs new file mode 100644 index 000000000..03a995ded --- /dev/null +++ b/src/IoTHub.Portal.Infrastructure/Repositories/RoleRepository.cs @@ -0,0 +1,26 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Infrastructure.Repositories +{ + using System.Linq.Expressions; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Repositories; + using Microsoft.EntityFrameworkCore; + + public class RoleRepository : GenericRepository, IRoleRepository + { + public RoleRepository(PortalDbContext context) : base(context) + { + } + public async Task GetByNameAsync(string roleName, params Expression>[] includeProperties) + { + IQueryable query = context.Roles; + foreach (var includeProperty in includeProperties) + { + query = query.Include(includeProperty); + } + return await query.FirstOrDefaultAsync(r => r.Name == roleName); + } + } +} diff --git a/src/IoTHub.Portal.Infrastructure/Repositories/UserRepository.cs b/src/IoTHub.Portal.Infrastructure/Repositories/UserRepository.cs new file mode 100644 index 000000000..6f587332f --- /dev/null +++ b/src/IoTHub.Portal.Infrastructure/Repositories/UserRepository.cs @@ -0,0 +1,27 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Infrastructure.Repositories +{ + using System.Linq.Expressions; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Repositories; + using Microsoft.EntityFrameworkCore; + + public class UserRepository : GenericRepository, IUserRepository + { + public UserRepository(PortalDbContext context) : base(context) + { + } + + public async Task GetByNameAsync(string userName, params Expression>[] includeProperties) + { + IQueryable query = context.Users; + foreach (var includeProperty in includeProperties) + { + query = query.Include(includeProperty); + } + return await query.FirstOrDefaultAsync(u => u.Name == userName); + } + } +} diff --git a/src/IoTHub.Portal.Infrastructure/Services/AwsDeviceModelService.cs b/src/IoTHub.Portal.Infrastructure/Services/AwsDeviceModelService.cs index 8c6ebe6fa..3153c2c74 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/AwsDeviceModelService.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/AwsDeviceModelService.cs @@ -9,15 +9,15 @@ namespace IoTHub.Portal.Infrastructure.Services using AutoMapper; using IoTHub.Portal.Application.Managers; using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Crosscutting; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Entities; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Domain.Repositories; using IoTHub.Portal.Domain.Shared; - using IoTHub.Portal.Infrastructure.Repositories; using IoTHub.Portal.Models.v10; using IoTHub.Portal.Shared.Models; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using Microsoft.AspNetCore.Http; diff --git a/src/IoTHub.Portal.Infrastructure/Services/DeviceServiceBase.cs b/src/IoTHub.Portal.Infrastructure/Services/DeviceServiceBase.cs index da748d4ff..db703f765 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/DeviceServiceBase.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/DeviceServiceBase.cs @@ -16,15 +16,14 @@ namespace IoTHub.Portal.Infrastructure.Services using IoTHub.Portal.Shared.Models.v10; using Domain.Entities; using Infrastructure; - using Infrastructure.Repositories; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Common.Exceptions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Models.v10; - using Shared.Models.v1._0; using Shared.Models.v10.Filters; using Device = Domain.Entities.Device; + using IoTHub.Portal.Crosscutting; public abstract class DeviceServiceBase : IDeviceService where TDto : IDeviceDetails diff --git a/src/IoTHub.Portal.Infrastructure/Services/EdgeDevicesServiceBase.cs b/src/IoTHub.Portal.Infrastructure/Services/EdgeDevicesServiceBase.cs index f919e8dc7..f08cf7fb0 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/EdgeDevicesServiceBase.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/EdgeDevicesServiceBase.cs @@ -11,12 +11,11 @@ namespace IoTHub.Portal.Infrastructure.Services using AutoMapper; using IoTHub.Portal.Application.Managers; using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Crosscutting; using IoTHub.Portal.Domain.Entities; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Domain.Repositories; - using IoTHub.Portal.Infrastructure.Repositories; using IoTHub.Portal.Models.v10; - using IoTHub.Portal.Shared.Models.v1._0; using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; diff --git a/src/IoTHub.Portal.Infrastructure/Services/EdgeModelService.cs b/src/IoTHub.Portal.Infrastructure/Services/EdgeModelService.cs index b8825e5a8..02dc64bd1 100644 --- a/src/IoTHub.Portal.Infrastructure/Services/EdgeModelService.cs +++ b/src/IoTHub.Portal.Infrastructure/Services/EdgeModelService.cs @@ -10,11 +10,11 @@ namespace IoTHub.Portal.Infrastructure.Services using AutoMapper; using IoTHub.Portal.Application.Managers; using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Crosscutting; using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Entities; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Domain.Repositories; - using IoTHub.Portal.Infrastructure.Repositories; using IoTHub.Portal.Models.v10; using IoTHub.Portal.Shared.Constants; using IoTHub.Portal.Shared.Models.v10; diff --git a/src/IoTHub.Portal.Infrastructure/Startup/IServiceCollectionExtension.cs b/src/IoTHub.Portal.Infrastructure/Startup/IServiceCollectionExtension.cs index 9a2b4102c..9c5c65133 100644 --- a/src/IoTHub.Portal.Infrastructure/Startup/IServiceCollectionExtension.cs +++ b/src/IoTHub.Portal.Infrastructure/Startup/IServiceCollectionExtension.cs @@ -11,8 +11,6 @@ namespace IoTHub.Portal.Infrastructure.Startup using IoTHub.Portal.Infrastructure.Repositories; using IoTHub.Portal.Infrastructure.Services; using IoTHub.Portal.Infrastructure.ServicesHealthCheck; - using IoTHub.Portal.Models.v10.LoRaWAN; - using IoTHub.Portal.Models.v10; using IoTHub.Portal.Shared.Constants; using EntityFramework.Exceptions.PostgreSQL; using Microsoft.EntityFrameworkCore; @@ -106,7 +104,13 @@ private static IServiceCollection ConfigureRepositories(this IServiceCollection .AddScoped() .AddScoped() .AddScoped() - .AddScoped(); + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped(); } private static IServiceCollection ConfigureServices(this IServiceCollection services) diff --git a/src/IoTHub.Portal.MySql/Migrations/20240108103650_RoleBasedAccessControl.Designer.cs b/src/IoTHub.Portal.MySql/Migrations/20240422124956_RoleBasedAccessControl-1_0.Designer.cs similarity index 89% rename from src/IoTHub.Portal.MySql/Migrations/20240108103650_RoleBasedAccessControl.Designer.cs rename to src/IoTHub.Portal.MySql/Migrations/20240422124956_RoleBasedAccessControl-1_0.Designer.cs index b999cbd0c..0d0585efa 100644 --- a/src/IoTHub.Portal.MySql/Migrations/20240108103650_RoleBasedAccessControl.Designer.cs +++ b/src/IoTHub.Portal.MySql/Migrations/20240422124956_RoleBasedAccessControl-1_0.Designer.cs @@ -11,8 +11,8 @@ namespace IoTHub.Portal.MySql.Migrations { [DbContext(typeof(PortalDbContext))] - [Migration("20240108103650_RoleBasedAccessControl")] - partial class RoleBasedAccessControl + [Migration("20240422124956_RoleBasedAccessControl-1_0")] + partial class RoleBasedAccessControl1_0 { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -22,12 +22,28 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasAnnotation("ProductVersion", "7.0.14") .HasAnnotation("Relational:MaxIdentifierLength", 64); + modelBuilder.Entity("GroupUser", b => + { + b.Property("GroupsId") + .HasColumnType("varchar(255)"); + + b.Property("MembersId") + .HasColumnType("varchar(255)"); + + b.HasKey("GroupsId", "MembersId"); + + b.HasIndex("MembersId"); + + b.ToTable("GroupUser"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => { b.Property("Id") .HasColumnType("varchar(255)"); - b.Property("GroupId") + b.Property("PrincipalId") + .IsRequired() .HasColumnType("varchar(255)"); b.Property("RoleId") @@ -38,17 +54,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("longtext"); - b.Property("UserId") - .HasColumnType("varchar(255)"); - b.HasKey("Id"); - b.HasIndex("GroupId"); + b.HasIndex("PrincipalId"); b.HasIndex("RoleId"); - b.HasIndex("UserId"); - b.ToTable("AccessControls"); }); @@ -389,15 +400,24 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("varchar(255)"); b.Property("Avatar") - .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") .HasColumnType("longtext"); b.Property("Name") .IsRequired() .HasColumnType("longtext"); + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("varchar(255)"); + b.HasKey("Id"); + b.HasIndex("PrincipalId") + .IsUnique(); + b.ToTable("Groups"); }); @@ -461,17 +481,33 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("LoRaDeviceTelemetry"); }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.ToTable("Principals"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => { b.Property("Id") .HasColumnType("varchar(255)"); + b.Property("Description") + .HasColumnType("longtext"); + b.Property("Name") .IsRequired() - .HasColumnType("longtext"); + .HasColumnType("varchar(255)"); b.HasKey("Id"); + b.HasIndex("Name") + .IsUnique(); + b.ToTable("Roles"); }); @@ -480,39 +516,33 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("varchar(255)"); + b.Property("Avatar") + .HasColumnType("longtext"); + b.Property("Email") .IsRequired() .HasColumnType("longtext"); - b.Property("Forename") - .IsRequired() + b.Property("FamilyName") .HasColumnType("longtext"); - b.Property("Name") + b.Property("GivenName") .IsRequired() .HasColumnType("longtext"); - b.HasKey("Id"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.UserMemberShip", b => - { - b.Property("UserId") - .HasColumnType("varchar(255)"); + b.Property("Name") + .HasColumnType("longtext"); - b.Property("GroupId") + b.Property("PrincipalId") + .IsRequired() .HasColumnType("varchar(255)"); - b.Property("Id") - .HasColumnType("longtext"); - - b.HasKey("UserId", "GroupId"); + b.HasKey("Id"); - b.HasIndex("GroupId"); + b.HasIndex("PrincipalId") + .IsUnique(); - b.ToTable("UserMemberShip"); + b.ToTable("Users"); }); modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => @@ -623,21 +653,36 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("LorawanDevices", (string)null); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + modelBuilder.Entity("GroupUser", b => { b.HasOne("IoTHub.Portal.Domain.Entities.Group", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("MembersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") .WithMany("AccessControls") - .HasForeignKey("GroupId"); + .HasForeignKey("PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("IoTHub.Portal.Domain.Entities.Role", "Role") .WithMany() .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) + .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("IoTHub.Portal.Domain.Entities.User", null) - .WithMany("AccessControls") - .HasForeignKey("UserId"); + b.Navigation("Principal"); b.Navigation("Role"); }); @@ -682,6 +727,17 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("DeviceModel"); }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("Group") + .HasForeignKey("IoTHub.Portal.Domain.Entities.Group", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => { b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) @@ -708,23 +764,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasForeignKey("LorawanDeviceId"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.UserMemberShip", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => { - b.HasOne("IoTHub.Portal.Domain.Entities.Group", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IoTHub.Portal.Domain.Entities.User", "User") - .WithMany("Groups") - .HasForeignKey("UserId") + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("User") + .HasForeignKey("IoTHub.Portal.Domain.Entities.User", "PrincipalId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Group"); - - b.Navigation("User"); + b.Navigation("Principal"); }); modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => @@ -760,11 +808,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Labels"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => { b.Navigation("AccessControls"); - b.Navigation("Members"); + b.Navigation("Group"); + + b.Navigation("User"); }); modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => @@ -772,13 +822,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Actions"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => - { - b.Navigation("AccessControls"); - - b.Navigation("Groups"); - }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => { b.Navigation("Telemetry"); diff --git a/src/IoTHub.Portal.MySql/Migrations/20240108103650_RoleBasedAccessControl.cs b/src/IoTHub.Portal.MySql/Migrations/20240422124956_RoleBasedAccessControl-1_0.cs similarity index 63% rename from src/IoTHub.Portal.MySql/Migrations/20240108103650_RoleBasedAccessControl.cs rename to src/IoTHub.Portal.MySql/Migrations/20240422124956_RoleBasedAccessControl-1_0.cs index 6ed98aa60..dd83f4e29 100644 --- a/src/IoTHub.Portal.MySql/Migrations/20240108103650_RoleBasedAccessControl.cs +++ b/src/IoTHub.Portal.MySql/Migrations/20240422124956_RoleBasedAccessControl-1_0.cs @@ -1,33 +1,27 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. - #nullable disable namespace IoTHub.Portal.MySql.Migrations { using Microsoft.EntityFrameworkCore.Migrations; - /// - public partial class RoleBasedAccessControl : Migration + public partial class RoleBasedAccessControl1_0 : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { _ = migrationBuilder.CreateTable( - name: "Groups", + name: "Principals", columns: table => new { Id = table.Column(type: "varchar(255)", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Name = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - Avatar = table.Column(type: "longtext", nullable: false) .Annotation("MySql:CharSet", "utf8mb4") }, constraints: table => { - _ = table.PrimaryKey("PK_Groups", x => x.Id); + _ = table.PrimaryKey("PK_Principals", x => x.Id); }) .Annotation("MySql:CharSet", "utf8mb4"); @@ -37,7 +31,9 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "varchar(255)", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), - Name = table.Column(type: "longtext", nullable: false) + Name = table.Column(type: "varchar(255)", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Description = table.Column(type: "longtext", nullable: true) .Annotation("MySql:CharSet", "utf8mb4") }, constraints: table => @@ -47,43 +43,60 @@ protected override void Up(MigrationBuilder migrationBuilder) .Annotation("MySql:CharSet", "utf8mb4"); _ = migrationBuilder.CreateTable( - name: "Users", + name: "Groups", columns: table => new { Id = table.Column(type: "varchar(255)", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), - Email = table.Column(type: "longtext", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), Name = table.Column(type: "longtext", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), - Forename = table.Column(type: "longtext", nullable: false) + Avatar = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Description = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PrincipalId = table.Column(type: "varchar(255)", nullable: false) .Annotation("MySql:CharSet", "utf8mb4") }, constraints: table => { - _ = table.PrimaryKey("PK_Users", x => x.Id); + _ = table.PrimaryKey("PK_Groups", x => x.Id); + _ = table.ForeignKey( + name: "FK_Groups_Principals_PrincipalId", + column: x => x.PrincipalId, + principalTable: "Principals", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }) .Annotation("MySql:CharSet", "utf8mb4"); _ = migrationBuilder.CreateTable( - name: "Actions", + name: "Users", columns: table => new { Id = table.Column(type: "varchar(255)", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), - Name = table.Column(type: "longtext", nullable: false) + Email = table.Column(type: "longtext", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), - RoleId = table.Column(type: "varchar(255)", nullable: true) + GivenName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Name = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + FamilyName = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Avatar = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + PrincipalId = table.Column(type: "varchar(255)", nullable: false) .Annotation("MySql:CharSet", "utf8mb4") }, constraints: table => { - _ = table.PrimaryKey("PK_Actions", x => x.Id); + _ = table.PrimaryKey("PK_Users", x => x.Id); _ = table.ForeignKey( - name: "FK_Actions_Roles_RoleId", - column: x => x.RoleId, - principalTable: "Roles", - principalColumn: "Id"); + name: "FK_Users_Principals_PrincipalId", + column: x => x.PrincipalId, + principalTable: "Principals", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }) .Annotation("MySql:CharSet", "utf8mb4"); @@ -97,56 +110,70 @@ protected override void Up(MigrationBuilder migrationBuilder) .Annotation("MySql:CharSet", "utf8mb4"), RoleId = table.Column(type: "varchar(255)", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), - GroupId = table.Column(type: "varchar(255)", nullable: true) - .Annotation("MySql:CharSet", "utf8mb4"), - UserId = table.Column(type: "varchar(255)", nullable: true) + PrincipalId = table.Column(type: "varchar(255)", nullable: false) .Annotation("MySql:CharSet", "utf8mb4") }, constraints: table => { _ = table.PrimaryKey("PK_AccessControls", x => x.Id); _ = table.ForeignKey( - name: "FK_AccessControls_Groups_GroupId", - column: x => x.GroupId, - principalTable: "Groups", - principalColumn: "Id"); + name: "FK_AccessControls_Principals_PrincipalId", + column: x => x.PrincipalId, + principalTable: "Principals", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); _ = table.ForeignKey( name: "FK_AccessControls_Roles_RoleId", column: x => x.RoleId, principalTable: "Roles", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Restrict); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + _ = migrationBuilder.CreateTable( + name: "Actions", + columns: table => new + { + Id = table.Column(type: "varchar(255)", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Name = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + RoleId = table.Column(type: "varchar(255)", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + _ = table.PrimaryKey("PK_Actions", x => x.Id); _ = table.ForeignKey( - name: "FK_AccessControls_Users_UserId", - column: x => x.UserId, - principalTable: "Users", + name: "FK_Actions_Roles_RoleId", + column: x => x.RoleId, + principalTable: "Roles", principalColumn: "Id"); }) .Annotation("MySql:CharSet", "utf8mb4"); _ = migrationBuilder.CreateTable( - name: "UserMemberShip", + name: "GroupUser", columns: table => new { - UserId = table.Column(type: "varchar(255)", nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"), - GroupId = table.Column(type: "varchar(255)", nullable: false) + GroupsId = table.Column(type: "varchar(255)", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), - Id = table.Column(type: "longtext", nullable: true) + MembersId = table.Column(type: "varchar(255)", nullable: false) .Annotation("MySql:CharSet", "utf8mb4") }, constraints: table => { - _ = table.PrimaryKey("PK_UserMemberShip", x => new { x.UserId, x.GroupId }); + _ = table.PrimaryKey("PK_GroupUser", x => new { x.GroupsId, x.MembersId }); _ = table.ForeignKey( - name: "FK_UserMemberShip_Groups_GroupId", - column: x => x.GroupId, + name: "FK_GroupUser_Groups_GroupsId", + column: x => x.GroupsId, principalTable: "Groups", principalColumn: "Id", onDelete: ReferentialAction.Cascade); _ = table.ForeignKey( - name: "FK_UserMemberShip_Users_UserId", - column: x => x.UserId, + name: "FK_GroupUser_Users_MembersId", + column: x => x.MembersId, principalTable: "Users", principalColumn: "Id", onDelete: ReferentialAction.Cascade); @@ -154,29 +181,42 @@ protected override void Up(MigrationBuilder migrationBuilder) .Annotation("MySql:CharSet", "utf8mb4"); _ = migrationBuilder.CreateIndex( - name: "IX_AccessControls_GroupId", + name: "IX_AccessControls_PrincipalId", table: "AccessControls", - column: "GroupId"); + column: "PrincipalId"); _ = migrationBuilder.CreateIndex( name: "IX_AccessControls_RoleId", table: "AccessControls", column: "RoleId"); - _ = migrationBuilder.CreateIndex( - name: "IX_AccessControls_UserId", - table: "AccessControls", - column: "UserId"); - _ = migrationBuilder.CreateIndex( name: "IX_Actions_RoleId", table: "Actions", column: "RoleId"); _ = migrationBuilder.CreateIndex( - name: "IX_UserMemberShip_GroupId", - table: "UserMemberShip", - column: "GroupId"); + name: "IX_Groups_PrincipalId", + table: "Groups", + column: "PrincipalId", + unique: true); + + _ = migrationBuilder.CreateIndex( + name: "IX_GroupUser_MembersId", + table: "GroupUser", + column: "MembersId"); + + _ = migrationBuilder.CreateIndex( + name: "IX_Roles_Name", + table: "Roles", + column: "Name", + unique: true); + + _ = migrationBuilder.CreateIndex( + name: "IX_Users_PrincipalId", + table: "Users", + column: "PrincipalId", + unique: true); } /// @@ -189,7 +229,7 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "Actions"); _ = migrationBuilder.DropTable( - name: "UserMemberShip"); + name: "GroupUser"); _ = migrationBuilder.DropTable( name: "Roles"); @@ -199,6 +239,9 @@ protected override void Down(MigrationBuilder migrationBuilder) _ = migrationBuilder.DropTable( name: "Users"); + + _ = migrationBuilder.DropTable( + name: "Principals"); } } } diff --git a/src/IoTHub.Portal.MySql/Migrations/20240426125508_RoleBasedAccessControl-1_1.Designer.cs b/src/IoTHub.Portal.MySql/Migrations/20240426125508_RoleBasedAccessControl-1_1.Designer.cs new file mode 100644 index 000000000..809f1259f --- /dev/null +++ b/src/IoTHub.Portal.MySql/Migrations/20240426125508_RoleBasedAccessControl-1_1.Designer.cs @@ -0,0 +1,841 @@ +// +using System; +using IoTHub.Portal.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IoTHub.Portal.MySql.Migrations +{ + [DbContext(typeof(PortalDbContext))] + [Migration("20240426125508_RoleBasedAccessControl-1_1")] + partial class RoleBasedAccessControl1_1 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.14") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("GroupUser", b => + { + b.Property("GroupsId") + .HasColumnType("varchar(255)"); + + b.Property("MembersId") + .HasColumnType("varchar(255)"); + + b.HasKey("GroupsId", "MembersId"); + + b.HasIndex("MembersId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("PrincipalId"); + + b.HasIndex("RoleId"); + + b.ToTable("AccessControls"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Action", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("Actions"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Concentrator", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ClientThumbprint") + .HasColumnType("longtext"); + + b.Property("DeviceType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsConnected") + .HasColumnType("tinyint(1)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LoraRegion") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Concentrators"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("IsConnected") + .HasColumnType("tinyint(1)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StatusUpdatedTime") + .HasColumnType("datetime(6)"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeviceModelId"); + + b.ToTable("Devices", (string)null); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModel", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ABPRelaxMode") + .HasColumnType("tinyint(1)"); + + b.Property("AppEUI") + .HasColumnType("longtext"); + + b.Property("ClassType") + .HasColumnType("int"); + + b.Property("Deduplication") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("Downlink") + .HasColumnType("tinyint(1)"); + + b.Property("IsBuiltin") + .HasColumnType("tinyint(1)"); + + b.Property("KeepAliveTimeout") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PreferredWindow") + .HasColumnType("int"); + + b.Property("RXDelay") + .HasColumnType("int"); + + b.Property("SensorDecoder") + .HasColumnType("longtext"); + + b.Property("SupportLoRaFeatures") + .HasColumnType("tinyint(1)"); + + b.Property("UseOTAA") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("DeviceModels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Confirmed") + .HasColumnType("tinyint(1)"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Frame") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsBuiltin") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Port") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelCommands"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModelProperty", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsWritable") + .HasColumnType("tinyint(1)"); + + b.Property("ModelId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("PropertyType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelProperties"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTag", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Label") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Required") + .HasColumnType("tinyint(1)"); + + b.Property("Searchable") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("DeviceTags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("DeviceId") + .HasColumnType("varchar(255)"); + + b.Property("EdgeDeviceId") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("EdgeDeviceId"); + + b.ToTable("DeviceTagValues"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConnectionState") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NbDevices") + .HasColumnType("int"); + + b.Property("NbModules") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("longtext"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeviceModelId"); + + b.ToTable("EdgeDevices"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ExternalIdentifier") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("EdgeDeviceModelId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ModuleName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModelCommands"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Avatar") + .HasColumnType("longtext"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DeviceId") + .HasColumnType("varchar(255)"); + + b.Property("DeviceModelId") + .HasColumnType("varchar(255)"); + + b.Property("EdgeDeviceId") + .HasColumnType("varchar(255)"); + + b.Property("EdgeDeviceModelId") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("DeviceModelId"); + + b.HasIndex("EdgeDeviceId"); + + b.HasIndex("EdgeDeviceModelId"); + + b.ToTable("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LoRaDeviceTelemetry", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("EnqueuedTime") + .HasColumnType("datetime(6)"); + + b.Property("LorawanDeviceId") + .HasColumnType("varchar(255)"); + + b.Property("Telemetry") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LorawanDeviceId"); + + b.ToTable("LoRaDeviceTelemetry"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.ToTable("Principals"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Avatar") + .HasColumnType("longtext"); + + b.Property("Email") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("FamilyName") + .HasColumnType("longtext"); + + b.Property("GivenName") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("GivenName") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("FriendlyName") + .HasColumnType("longtext"); + + b.Property("Xml") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.HasBaseType("IoTHub.Portal.Domain.Entities.Device"); + + b.Property("ABPRelaxMode") + .HasColumnType("tinyint(1)"); + + b.Property("AlreadyLoggedInOnce") + .HasColumnType("tinyint(1)"); + + b.Property("AppEUI") + .HasColumnType("longtext"); + + b.Property("AppKey") + .HasColumnType("longtext"); + + b.Property("AppSKey") + .HasColumnType("longtext"); + + b.Property("ClassType") + .HasColumnType("int"); + + b.Property("DataRate") + .HasColumnType("longtext"); + + b.Property("Deduplication") + .HasColumnType("int"); + + b.Property("DevAddr") + .HasColumnType("longtext"); + + b.Property("Downlink") + .HasColumnType("tinyint(1)"); + + b.Property("FCntDownStart") + .HasColumnType("int"); + + b.Property("FCntResetCounter") + .HasColumnType("int"); + + b.Property("FCntUpStart") + .HasColumnType("int"); + + b.Property("GatewayID") + .HasColumnType("longtext"); + + b.Property("KeepAliveTimeout") + .HasColumnType("int"); + + b.Property("NbRep") + .HasColumnType("longtext"); + + b.Property("NwkSKey") + .HasColumnType("longtext"); + + b.Property("PreferredWindow") + .HasColumnType("int"); + + b.Property("RX1DROffset") + .HasColumnType("int"); + + b.Property("RX2DataRate") + .HasColumnType("int"); + + b.Property("RXDelay") + .HasColumnType("int"); + + b.Property("ReportedRX1DROffset") + .HasColumnType("longtext"); + + b.Property("ReportedRX2DataRate") + .HasColumnType("longtext"); + + b.Property("ReportedRXDelay") + .HasColumnType("longtext"); + + b.Property("SensorDecoder") + .HasColumnType("longtext"); + + b.Property("Supports32BitFCnt") + .HasColumnType("tinyint(1)"); + + b.Property("TxPower") + .HasColumnType("longtext"); + + b.Property("UseOTAA") + .HasColumnType("tinyint(1)"); + + b.ToTable("LorawanDevices", (string)null); + }); + + modelBuilder.Entity("GroupUser", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Group", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("MembersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithMany("AccessControls") + .HasForeignKey("PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Principal"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Action", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Role", null) + .WithMany("Actions") + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.DeviceModel", "DeviceModel") + .WithMany() + .HasForeignKey("DeviceModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DeviceModel"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithMany("Tags") + .HasForeignKey("DeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDevice", null) + .WithMany("Tags") + .HasForeignKey("EdgeDeviceId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", "DeviceModel") + .WithMany() + .HasForeignKey("DeviceModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DeviceModel"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("Group") + .HasForeignKey("IoTHub.Portal.Domain.Entities.Group", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithMany("Labels") + .HasForeignKey("DeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.DeviceModel", null) + .WithMany("Labels") + .HasForeignKey("DeviceModelId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDevice", null) + .WithMany("Labels") + .HasForeignKey("EdgeDeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", null) + .WithMany("Labels") + .HasForeignKey("EdgeDeviceModelId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LoRaDeviceTelemetry", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.LorawanDevice", null) + .WithMany("Telemetry") + .HasForeignKey("LorawanDeviceId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("User") + .HasForeignKey("IoTHub.Portal.Domain.Entities.User", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithOne() + .HasForeignKey("IoTHub.Portal.Domain.Entities.LorawanDevice", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.Navigation("Labels"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModel", b => + { + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.Navigation("Labels"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", b => + { + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Navigation("AccessControls"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => + { + b.Navigation("Actions"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.Navigation("Telemetry"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IoTHub.Portal.MySql/Migrations/20240426125508_RoleBasedAccessControl-1_1.cs b/src/IoTHub.Portal.MySql/Migrations/20240426125508_RoleBasedAccessControl-1_1.cs new file mode 100644 index 000000000..0ea15f157 --- /dev/null +++ b/src/IoTHub.Portal.MySql/Migrations/20240426125508_RoleBasedAccessControl-1_1.cs @@ -0,0 +1,111 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#nullable disable + +namespace IoTHub.Portal.MySql.Migrations +{ + using Microsoft.EntityFrameworkCore.Migrations; + + /// + public partial class RoleBasedAccessControl1_1 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.AlterColumn( + name: "GivenName", + table: "Users", + type: "varchar(255)", + nullable: false, + oldClrType: typeof(string), + oldType: "longtext") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + _ = migrationBuilder.AlterColumn( + name: "Email", + table: "Users", + type: "varchar(255)", + nullable: false, + oldClrType: typeof(string), + oldType: "longtext") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + _ = migrationBuilder.AlterColumn( + name: "Name", + table: "Groups", + type: "varchar(255)", + nullable: false, + oldClrType: typeof(string), + oldType: "longtext") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + _ = migrationBuilder.CreateIndex( + name: "IX_Users_Email", + table: "Users", + column: "Email", + unique: true); + + _ = migrationBuilder.CreateIndex( + name: "IX_Users_GivenName", + table: "Users", + column: "GivenName", + unique: true); + + _ = migrationBuilder.CreateIndex( + name: "IX_Groups_Name", + table: "Groups", + column: "Name", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.DropIndex( + name: "IX_Users_Email", + table: "Users"); + + _ = migrationBuilder.DropIndex( + name: "IX_Users_GivenName", + table: "Users"); + + _ = migrationBuilder.DropIndex( + name: "IX_Groups_Name", + table: "Groups"); + + _ = migrationBuilder.AlterColumn( + name: "GivenName", + table: "Users", + type: "longtext", + nullable: false, + oldClrType: typeof(string), + oldType: "varchar( )") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + _ = migrationBuilder.AlterColumn( + name: "Email", + table: "Users", + type: "longtext", + nullable: false, + oldClrType: typeof(string), + oldType: "varchar(255)") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + _ = migrationBuilder.AlterColumn( + name: "Name", + table: "Groups", + type: "longtext", + nullable: false, + oldClrType: typeof(string), + oldType: "varchar(255)") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + } + } +} diff --git a/src/IoTHub.Portal.MySql/Migrations/20240604111205_RoleBasedAccessControl-1_2.Designer.cs b/src/IoTHub.Portal.MySql/Migrations/20240604111205_RoleBasedAccessControl-1_2.Designer.cs new file mode 100644 index 000000000..fbced39af --- /dev/null +++ b/src/IoTHub.Portal.MySql/Migrations/20240604111205_RoleBasedAccessControl-1_2.Designer.cs @@ -0,0 +1,848 @@ +// +using System; +using IoTHub.Portal.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace IoTHub.Portal.MySql.Migrations +{ + [DbContext(typeof(PortalDbContext))] + [Migration("20240604111205_RoleBasedAccessControl-1_2")] + partial class RoleBasedAccessControl1_2 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("GroupUser", b => + { + b.Property("GroupsId") + .HasColumnType("varchar(255)"); + + b.Property("MembersId") + .HasColumnType("varchar(255)"); + + b.HasKey("GroupsId", "MembersId"); + + b.HasIndex("MembersId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("PrincipalId"); + + b.HasIndex("RoleId"); + + b.ToTable("AccessControls"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Action", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("Actions"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Concentrator", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ClientThumbprint") + .HasColumnType("longtext"); + + b.Property("DeviceType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsConnected") + .HasColumnType("tinyint(1)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LoraRegion") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Concentrators"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("IsConnected") + .HasColumnType("tinyint(1)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StatusUpdatedTime") + .HasColumnType("datetime(6)"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeviceModelId"); + + b.ToTable("Devices", (string)null); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModel", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ABPRelaxMode") + .HasColumnType("tinyint(1)"); + + b.Property("AppEUI") + .HasColumnType("longtext"); + + b.Property("ClassType") + .HasColumnType("int"); + + b.Property("Deduplication") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("Downlink") + .HasColumnType("tinyint(1)"); + + b.Property("IsBuiltin") + .HasColumnType("tinyint(1)"); + + b.Property("KeepAliveTimeout") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PreferredWindow") + .HasColumnType("int"); + + b.Property("RXDelay") + .HasColumnType("int"); + + b.Property("SensorDecoder") + .HasColumnType("longtext"); + + b.Property("SupportLoRaFeatures") + .HasColumnType("tinyint(1)"); + + b.Property("UseOTAA") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("DeviceModels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Confirmed") + .HasColumnType("tinyint(1)"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Frame") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsBuiltin") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Port") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelCommands"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModelProperty", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsWritable") + .HasColumnType("tinyint(1)"); + + b.Property("ModelId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("PropertyType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelProperties"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTag", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Label") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Required") + .HasColumnType("tinyint(1)"); + + b.Property("Searchable") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("DeviceTags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("DeviceId") + .HasColumnType("varchar(255)"); + + b.Property("EdgeDeviceId") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("EdgeDeviceId"); + + b.ToTable("DeviceTagValues"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConnectionState") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NbDevices") + .HasColumnType("int"); + + b.Property("NbModules") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("longtext"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DeviceModelId"); + + b.ToTable("EdgeDevices"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ExternalIdentifier") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("EdgeDeviceModelId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ModuleName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModelCommands"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DeviceId") + .HasColumnType("varchar(255)"); + + b.Property("DeviceModelId") + .HasColumnType("varchar(255)"); + + b.Property("EdgeDeviceId") + .HasColumnType("varchar(255)"); + + b.Property("EdgeDeviceModelId") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("DeviceModelId"); + + b.HasIndex("EdgeDeviceId"); + + b.HasIndex("EdgeDeviceModelId"); + + b.ToTable("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LoRaDeviceTelemetry", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("EnqueuedTime") + .HasColumnType("datetime(6)"); + + b.Property("LorawanDeviceId") + .HasColumnType("varchar(255)"); + + b.Property("Telemetry") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LorawanDeviceId"); + + b.ToTable("LoRaDeviceTelemetry"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.ToTable("Principals"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("Avatar") + .HasColumnType("longtext"); + + b.Property("Email") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("FamilyName") + .HasColumnType("longtext"); + + b.Property("GivenName") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("GivenName") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("FriendlyName") + .HasColumnType("longtext"); + + b.Property("Xml") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.HasBaseType("IoTHub.Portal.Domain.Entities.Device"); + + b.Property("ABPRelaxMode") + .HasColumnType("tinyint(1)"); + + b.Property("AlreadyLoggedInOnce") + .HasColumnType("tinyint(1)"); + + b.Property("AppEUI") + .HasColumnType("longtext"); + + b.Property("AppKey") + .HasColumnType("longtext"); + + b.Property("AppSKey") + .HasColumnType("longtext"); + + b.Property("ClassType") + .HasColumnType("int"); + + b.Property("DataRate") + .HasColumnType("longtext"); + + b.Property("Deduplication") + .HasColumnType("int"); + + b.Property("DevAddr") + .HasColumnType("longtext"); + + b.Property("Downlink") + .HasColumnType("tinyint(1)"); + + b.Property("FCntDownStart") + .HasColumnType("int"); + + b.Property("FCntResetCounter") + .HasColumnType("int"); + + b.Property("FCntUpStart") + .HasColumnType("int"); + + b.Property("GatewayID") + .HasColumnType("longtext"); + + b.Property("KeepAliveTimeout") + .HasColumnType("int"); + + b.Property("NbRep") + .HasColumnType("longtext"); + + b.Property("NwkSKey") + .HasColumnType("longtext"); + + b.Property("PreferredWindow") + .HasColumnType("int"); + + b.Property("RX1DROffset") + .HasColumnType("int"); + + b.Property("RX2DataRate") + .HasColumnType("int"); + + b.Property("RXDelay") + .HasColumnType("int"); + + b.Property("ReportedRX1DROffset") + .HasColumnType("longtext"); + + b.Property("ReportedRX2DataRate") + .HasColumnType("longtext"); + + b.Property("ReportedRXDelay") + .HasColumnType("longtext"); + + b.Property("SensorDecoder") + .HasColumnType("longtext"); + + b.Property("Supports32BitFCnt") + .HasColumnType("tinyint(1)"); + + b.Property("TxPower") + .HasColumnType("longtext"); + + b.Property("UseOTAA") + .HasColumnType("tinyint(1)"); + + b.ToTable("LorawanDevices", (string)null); + }); + + modelBuilder.Entity("GroupUser", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Group", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("MembersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithMany("AccessControls") + .HasForeignKey("PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Principal"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Action", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Role", null) + .WithMany("Actions") + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.DeviceModel", "DeviceModel") + .WithMany() + .HasForeignKey("DeviceModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DeviceModel"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithMany("Tags") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDevice", null) + .WithMany("Tags") + .HasForeignKey("EdgeDeviceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", "DeviceModel") + .WithMany() + .HasForeignKey("DeviceModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DeviceModel"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("Group") + .HasForeignKey("IoTHub.Portal.Domain.Entities.Group", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithMany("Labels") + .HasForeignKey("DeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.DeviceModel", null) + .WithMany("Labels") + .HasForeignKey("DeviceModelId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDevice", null) + .WithMany("Labels") + .HasForeignKey("EdgeDeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", null) + .WithMany("Labels") + .HasForeignKey("EdgeDeviceModelId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LoRaDeviceTelemetry", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.LorawanDevice", null) + .WithMany("Telemetry") + .HasForeignKey("LorawanDeviceId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("User") + .HasForeignKey("IoTHub.Portal.Domain.Entities.User", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithOne() + .HasForeignKey("IoTHub.Portal.Domain.Entities.LorawanDevice", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.Navigation("Labels"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModel", b => + { + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.Navigation("Labels"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", b => + { + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Navigation("AccessControls"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => + { + b.Navigation("Actions"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.Navigation("Telemetry"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IoTHub.Portal.MySql/Migrations/20240604111205_RoleBasedAccessControl-1_2.cs b/src/IoTHub.Portal.MySql/Migrations/20240604111205_RoleBasedAccessControl-1_2.cs new file mode 100644 index 000000000..248d2be94 --- /dev/null +++ b/src/IoTHub.Portal.MySql/Migrations/20240604111205_RoleBasedAccessControl-1_2.cs @@ -0,0 +1,52 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +#nullable disable + +namespace IoTHub.Portal.MySql.Migrations +{ + using Microsoft.EntityFrameworkCore.Migrations; + /// + public partial class RoleBasedAccessControl1_2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.DropColumn( + name: "Avatar", + table: "Groups"); + + _ = migrationBuilder.AddColumn( + name: "Color", + table: "Roles", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + + _ = migrationBuilder.AddColumn( + name: "Color", + table: "Groups", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.DropColumn( + name: "Color", + table: "Roles"); + + _ = migrationBuilder.DropColumn( + name: "Color", + table: "Groups"); + + _ = migrationBuilder.AddColumn( + name: "Avatar", + table: "Groups", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + } +} diff --git a/src/IoTHub.Portal.MySql/Migrations/PortalDbContextModelSnapshot.cs b/src/IoTHub.Portal.MySql/Migrations/PortalDbContextModelSnapshot.cs index bacbfdcd6..a746e97e6 100644 --- a/src/IoTHub.Portal.MySql/Migrations/PortalDbContextModelSnapshot.cs +++ b/src/IoTHub.Portal.MySql/Migrations/PortalDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using IoTHub.Portal.Infrastructure; using Microsoft.EntityFrameworkCore; @@ -19,12 +19,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasAnnotation("ProductVersion", "8.0.2") .HasAnnotation("Relational:MaxIdentifierLength", 64); + modelBuilder.Entity("GroupUser", b => + { + b.Property("GroupsId") + .HasColumnType("varchar(255)"); + + b.Property("MembersId") + .HasColumnType("varchar(255)"); + + b.HasKey("GroupsId", "MembersId"); + + b.HasIndex("MembersId"); + + b.ToTable("GroupUser"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => { b.Property("Id") .HasColumnType("varchar(255)"); - b.Property("GroupId") + b.Property("PrincipalId") + .IsRequired() .HasColumnType("varchar(255)"); b.Property("RoleId") @@ -35,17 +51,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("longtext"); - b.Property("UserId") - .HasColumnType("varchar(255)"); - b.HasKey("Id"); - b.HasIndex("GroupId"); + b.HasIndex("PrincipalId"); b.HasIndex("RoleId"); - b.HasIndex("UserId"); - b.ToTable("AccessControls"); }); @@ -385,16 +396,29 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("varchar(255)"); - b.Property("Avatar") + b.Property("Color") .IsRequired() .HasColumnType("longtext"); + b.Property("Description") + .HasColumnType("longtext"); + b.Property("Name") .IsRequired() - .HasColumnType("longtext"); + .HasColumnType("varchar(255)"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("varchar(255)"); b.HasKey("Id"); + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + b.ToTable("Groups"); }); @@ -458,58 +482,78 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("LoRaDeviceTelemetry"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => { b.Property("Id") .HasColumnType("varchar(255)"); - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - b.HasKey("Id"); - b.ToTable("Roles"); + b.ToTable("Principals"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => { b.Property("Id") .HasColumnType("varchar(255)"); - b.Property("Email") + b.Property("Color") .IsRequired() .HasColumnType("longtext"); - b.Property("Forename") - .IsRequired() + b.Property("Description") .HasColumnType("longtext"); b.Property("Name") .IsRequired() - .HasColumnType("longtext"); + .HasColumnType("varchar(255)"); b.HasKey("Id"); - b.ToTable("Users"); + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Roles"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.UserMemberShip", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => { - b.Property("UserId") + b.Property("Id") .HasColumnType("varchar(255)"); - b.Property("GroupId") + b.Property("Avatar") + .HasColumnType("longtext"); + + b.Property("Email") + .IsRequired() .HasColumnType("varchar(255)"); - b.Property("Id") + b.Property("FamilyName") .HasColumnType("longtext"); - b.HasKey("UserId", "GroupId"); + b.Property("GivenName") + .IsRequired() + .HasColumnType("varchar(255)"); - b.HasIndex("GroupId"); + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); - b.ToTable("UserMemberShip"); + b.HasIndex("GivenName") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + + b.ToTable("Users"); }); modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => @@ -620,21 +664,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("LorawanDevices", (string)null); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + modelBuilder.Entity("GroupUser", b => { b.HasOne("IoTHub.Portal.Domain.Entities.Group", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("MembersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") .WithMany("AccessControls") - .HasForeignKey("GroupId"); + .HasForeignKey("PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("IoTHub.Portal.Domain.Entities.Role", "Role") .WithMany() .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) + .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("IoTHub.Portal.Domain.Entities.User", null) - .WithMany("AccessControls") - .HasForeignKey("UserId"); + b.Navigation("Principal"); b.Navigation("Role"); }); @@ -681,6 +740,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DeviceModel"); }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("Group") + .HasForeignKey("IoTHub.Portal.Domain.Entities.Group", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => { b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) @@ -707,23 +777,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("LorawanDeviceId"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.UserMemberShip", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => { - b.HasOne("IoTHub.Portal.Domain.Entities.Group", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IoTHub.Portal.Domain.Entities.User", "User") - .WithMany("Groups") - .HasForeignKey("UserId") + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("User") + .HasForeignKey("IoTHub.Portal.Domain.Entities.User", "PrincipalId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Group"); - - b.Navigation("User"); + b.Navigation("Principal"); }); modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => @@ -759,11 +821,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Labels"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => { b.Navigation("AccessControls"); - b.Navigation("Members"); + b.Navigation("Group"); + + b.Navigation("User"); }); modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => @@ -771,13 +835,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Actions"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => - { - b.Navigation("AccessControls"); - - b.Navigation("Groups"); - }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => { b.Navigation("Telemetry"); diff --git a/src/IoTHub.Portal.Postgres/Migrations/20240108102059_RoleBaseAccessControlV1.Designer.cs b/src/IoTHub.Portal.Postgres/Migrations/20240422124406_RoleBasedAccessControl-1_0.Designer.cs similarity index 89% rename from src/IoTHub.Portal.Postgres/Migrations/20240108102059_RoleBaseAccessControlV1.Designer.cs rename to src/IoTHub.Portal.Postgres/Migrations/20240422124406_RoleBasedAccessControl-1_0.Designer.cs index cd27c69bb..2448ddd2c 100644 --- a/src/IoTHub.Portal.Postgres/Migrations/20240108102059_RoleBaseAccessControlV1.Designer.cs +++ b/src/IoTHub.Portal.Postgres/Migrations/20240422124406_RoleBasedAccessControl-1_0.Designer.cs @@ -12,8 +12,8 @@ namespace IoTHub.Portal.Postgres.Migrations { [DbContext(typeof(PortalDbContext))] - [Migration("20240108102059_RoleBaseAccessControlV1")] - partial class RoleBaseAccessControlV1 + [Migration("20240422124406_RoleBasedAccessControl-1_0")] + partial class RoleBasedAccessControl1_0 { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -25,12 +25,28 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("GroupUser", b => + { + b.Property("GroupsId") + .HasColumnType("text"); + + b.Property("MembersId") + .HasColumnType("text"); + + b.HasKey("GroupsId", "MembersId"); + + b.HasIndex("MembersId"); + + b.ToTable("GroupUser"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => { b.Property("Id") .HasColumnType("text"); - b.Property("GroupId") + b.Property("PrincipalId") + .IsRequired() .HasColumnType("text"); b.Property("RoleId") @@ -41,17 +57,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); - b.Property("UserId") - .HasColumnType("text"); - b.HasKey("Id"); - b.HasIndex("GroupId"); + b.HasIndex("PrincipalId"); b.HasIndex("RoleId"); - b.HasIndex("UserId"); - b.ToTable("AccessControls"); }); @@ -392,15 +403,24 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("text"); b.Property("Avatar") - .IsRequired() + .HasColumnType("text"); + + b.Property("Description") .HasColumnType("text"); b.Property("Name") .IsRequired() .HasColumnType("text"); + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("text"); + b.HasKey("Id"); + b.HasIndex("PrincipalId") + .IsUnique(); + b.ToTable("Groups"); }); @@ -464,17 +484,33 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("LoRaDeviceTelemetry"); }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Principals"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => { b.Property("Id") .HasColumnType("text"); + b.Property("Description") + .HasColumnType("text"); + b.Property("Name") .IsRequired() .HasColumnType("text"); b.HasKey("Id"); + b.HasIndex("Name") + .IsUnique(); + b.ToTable("Roles"); }); @@ -483,39 +519,33 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("text"); - b.Property("Email") - .IsRequired() + b.Property("Avatar") .HasColumnType("text"); - b.Property("Forename") + b.Property("Email") .IsRequired() .HasColumnType("text"); - b.Property("Name") - .IsRequired() + b.Property("FamilyName") .HasColumnType("text"); - b.HasKey("Id"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.UserMemberShip", b => - { - b.Property("UserId") + b.Property("GivenName") + .IsRequired() .HasColumnType("text"); - b.Property("GroupId") + b.Property("Name") .HasColumnType("text"); - b.Property("Id") + b.Property("PrincipalId") + .IsRequired() .HasColumnType("text"); - b.HasKey("UserId", "GroupId"); + b.HasKey("Id"); - b.HasIndex("GroupId"); + b.HasIndex("PrincipalId") + .IsUnique(); - b.ToTable("UserMemberShip"); + b.ToTable("Users"); }); modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => @@ -628,21 +658,36 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("LorawanDevices", (string)null); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + modelBuilder.Entity("GroupUser", b => { b.HasOne("IoTHub.Portal.Domain.Entities.Group", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("MembersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") .WithMany("AccessControls") - .HasForeignKey("GroupId"); + .HasForeignKey("PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("IoTHub.Portal.Domain.Entities.Role", "Role") .WithMany() .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) + .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("IoTHub.Portal.Domain.Entities.User", null) - .WithMany("AccessControls") - .HasForeignKey("UserId"); + b.Navigation("Principal"); b.Navigation("Role"); }); @@ -687,6 +732,17 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("DeviceModel"); }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("Group") + .HasForeignKey("IoTHub.Portal.Domain.Entities.Group", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => { b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) @@ -713,23 +769,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasForeignKey("LorawanDeviceId"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.UserMemberShip", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => { - b.HasOne("IoTHub.Portal.Domain.Entities.Group", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IoTHub.Portal.Domain.Entities.User", "User") - .WithMany("Groups") - .HasForeignKey("UserId") + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("User") + .HasForeignKey("IoTHub.Portal.Domain.Entities.User", "PrincipalId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Group"); - - b.Navigation("User"); + b.Navigation("Principal"); }); modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => @@ -765,11 +813,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Labels"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => { b.Navigation("AccessControls"); - b.Navigation("Members"); + b.Navigation("Group"); + + b.Navigation("User"); }); modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => @@ -777,13 +827,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Actions"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => - { - b.Navigation("AccessControls"); - - b.Navigation("Groups"); - }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => { b.Navigation("Telemetry"); diff --git a/src/IoTHub.Portal.Postgres/Migrations/20240108102059_RoleBaseAccessControlV1.cs b/src/IoTHub.Portal.Postgres/Migrations/20240422124406_RoleBasedAccessControl-1_0.cs similarity index 61% rename from src/IoTHub.Portal.Postgres/Migrations/20240108102059_RoleBaseAccessControlV1.cs rename to src/IoTHub.Portal.Postgres/Migrations/20240422124406_RoleBasedAccessControl-1_0.cs index 8c69dfac3..46341d675 100644 --- a/src/IoTHub.Portal.Postgres/Migrations/20240108102059_RoleBaseAccessControlV1.cs +++ b/src/IoTHub.Portal.Postgres/Migrations/20240422124406_RoleBasedAccessControl-1_0.cs @@ -7,23 +7,22 @@ namespace IoTHub.Portal.Postgres.Migrations { using Microsoft.EntityFrameworkCore.Migrations; + /// - public partial class RoleBaseAccessControlV1 : Migration + public partial class RoleBasedAccessControl1_0 : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { _ = migrationBuilder.CreateTable( - name: "Groups", + name: "Principals", columns: table => new { - Id = table.Column(type: "text", nullable: false), - Name = table.Column(type: "text", nullable: false), - Avatar = table.Column(type: "text", nullable: false) + Id = table.Column(type: "text", nullable: false) }, constraints: table => { - _ = table.PrimaryKey("PK_Groups", x => x.Id); + _ = table.PrimaryKey("PK_Principals", x => x.Id); }); _ = migrationBuilder.CreateTable( @@ -31,7 +30,8 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "text", nullable: false), - Name = table.Column(type: "text", nullable: false) + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -39,35 +39,47 @@ protected override void Up(MigrationBuilder migrationBuilder) }); _ = migrationBuilder.CreateTable( - name: "Users", + name: "Groups", columns: table => new { Id = table.Column(type: "text", nullable: false), - Email = table.Column(type: "text", nullable: false), Name = table.Column(type: "text", nullable: false), - Forename = table.Column(type: "text", nullable: false) + Avatar = table.Column(type: "text", nullable: true), + Description = table.Column(type: "text", nullable: true), + PrincipalId = table.Column(type: "text", nullable: false) }, constraints: table => { - _ = table.PrimaryKey("PK_Users", x => x.Id); + _ = table.PrimaryKey("PK_Groups", x => x.Id); + _ = table.ForeignKey( + name: "FK_Groups_Principals_PrincipalId", + column: x => x.PrincipalId, + principalTable: "Principals", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); _ = migrationBuilder.CreateTable( - name: "Actions", + name: "Users", columns: table => new { Id = table.Column(type: "text", nullable: false), - Name = table.Column(type: "text", nullable: false), - RoleId = table.Column(type: "text", nullable: true) + Email = table.Column(type: "text", nullable: false), + GivenName = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: true), + FamilyName = table.Column(type: "text", nullable: true), + Avatar = table.Column(type: "text", nullable: true), + PrincipalId = table.Column(type: "text", nullable: false) }, constraints: table => { - _ = table.PrimaryKey("PK_Actions", x => x.Id); + _ = table.PrimaryKey("PK_Users", x => x.Id); _ = table.ForeignKey( - name: "FK_Actions_Roles_RoleId", - column: x => x.RoleId, - principalTable: "Roles", - principalColumn: "Id"); + name: "FK_Users_Principals_PrincipalId", + column: x => x.PrincipalId, + principalTable: "Principals", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); _ = migrationBuilder.CreateTable( @@ -77,79 +89,104 @@ protected override void Up(MigrationBuilder migrationBuilder) Id = table.Column(type: "text", nullable: false), Scope = table.Column(type: "text", nullable: false), RoleId = table.Column(type: "text", nullable: false), - GroupId = table.Column(type: "text", nullable: true), - UserId = table.Column(type: "text", nullable: true) + PrincipalId = table.Column(type: "text", nullable: false) }, constraints: table => { _ = table.PrimaryKey("PK_AccessControls", x => x.Id); _ = table.ForeignKey( - name: "FK_AccessControls_Groups_GroupId", - column: x => x.GroupId, - principalTable: "Groups", - principalColumn: "Id"); + name: "FK_AccessControls_Principals_PrincipalId", + column: x => x.PrincipalId, + principalTable: "Principals", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); _ = table.ForeignKey( name: "FK_AccessControls_Roles_RoleId", column: x => x.RoleId, principalTable: "Roles", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Restrict); + }); + + _ = migrationBuilder.CreateTable( + name: "Actions", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + RoleId = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + _ = table.PrimaryKey("PK_Actions", x => x.Id); _ = table.ForeignKey( - name: "FK_AccessControls_Users_UserId", - column: x => x.UserId, - principalTable: "Users", + name: "FK_Actions_Roles_RoleId", + column: x => x.RoleId, + principalTable: "Roles", principalColumn: "Id"); }); _ = migrationBuilder.CreateTable( - name: "UserMemberShip", + name: "GroupUser", columns: table => new { - UserId = table.Column(type: "text", nullable: false), - GroupId = table.Column(type: "text", nullable: false), - Id = table.Column(type: "text", nullable: true) + GroupsId = table.Column(type: "text", nullable: false), + MembersId = table.Column(type: "text", nullable: false) }, constraints: table => { - _ = table.PrimaryKey("PK_UserMemberShip", x => new { x.UserId, x.GroupId }); + _ = table.PrimaryKey("PK_GroupUser", x => new { x.GroupsId, x.MembersId }); _ = table.ForeignKey( - name: "FK_UserMemberShip_Groups_GroupId", - column: x => x.GroupId, + name: "FK_GroupUser_Groups_GroupsId", + column: x => x.GroupsId, principalTable: "Groups", principalColumn: "Id", onDelete: ReferentialAction.Cascade); _ = table.ForeignKey( - name: "FK_UserMemberShip_Users_UserId", - column: x => x.UserId, + name: "FK_GroupUser_Users_MembersId", + column: x => x.MembersId, principalTable: "Users", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); _ = migrationBuilder.CreateIndex( - name: "IX_AccessControls_GroupId", + name: "IX_AccessControls_PrincipalId", table: "AccessControls", - column: "GroupId"); + column: "PrincipalId"); _ = migrationBuilder.CreateIndex( name: "IX_AccessControls_RoleId", table: "AccessControls", column: "RoleId"); - _ = migrationBuilder.CreateIndex( - name: "IX_AccessControls_UserId", - table: "AccessControls", - column: "UserId"); - _ = migrationBuilder.CreateIndex( name: "IX_Actions_RoleId", table: "Actions", column: "RoleId"); _ = migrationBuilder.CreateIndex( - name: "IX_UserMemberShip_GroupId", - table: "UserMemberShip", - column: "GroupId"); + name: "IX_Groups_PrincipalId", + table: "Groups", + column: "PrincipalId", + unique: true); + + _ = migrationBuilder.CreateIndex( + name: "IX_GroupUser_MembersId", + table: "GroupUser", + column: "MembersId"); + + _ = migrationBuilder.CreateIndex( + name: "IX_Roles_Name", + table: "Roles", + column: "Name", + unique: true); + + _ = migrationBuilder.CreateIndex( + name: "IX_Users_PrincipalId", + table: "Users", + column: "PrincipalId", + unique: true); } /// @@ -162,7 +199,7 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "Actions"); _ = migrationBuilder.DropTable( - name: "UserMemberShip"); + name: "GroupUser"); _ = migrationBuilder.DropTable( name: "Roles"); @@ -172,6 +209,9 @@ protected override void Down(MigrationBuilder migrationBuilder) _ = migrationBuilder.DropTable( name: "Users"); + + _ = migrationBuilder.DropTable( + name: "Principals"); } } } diff --git a/src/IoTHub.Portal.Postgres/Migrations/20240426124632_RoleBasedAccessControl-1_1.Designer.cs b/src/IoTHub.Portal.Postgres/Migrations/20240426124632_RoleBasedAccessControl-1_1.Designer.cs new file mode 100644 index 000000000..c1dbc6cee --- /dev/null +++ b/src/IoTHub.Portal.Postgres/Migrations/20240426124632_RoleBasedAccessControl-1_1.Designer.cs @@ -0,0 +1,846 @@ +// +using System; +using IoTHub.Portal.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IoTHub.Portal.Postgres.Migrations +{ + [DbContext(typeof(PortalDbContext))] + [Migration("20240426124632_RoleBasedAccessControl-1_1")] + partial class RoleBasedAccessControl1_1 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.14") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("GroupUser", b => + { + b.Property("GroupsId") + .HasColumnType("text"); + + b.Property("MembersId") + .HasColumnType("text"); + + b.HasKey("GroupsId", "MembersId"); + + b.HasIndex("MembersId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("PrincipalId"); + + b.HasIndex("RoleId"); + + b.ToTable("AccessControls"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Action", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("Actions"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Concentrator", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientThumbprint") + .HasColumnType("text"); + + b.Property("DeviceType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConnected") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LoraRegion") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Concentrators"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConnected") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("StatusUpdatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeviceModelId"); + + b.ToTable("Devices", (string)null); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ABPRelaxMode") + .HasColumnType("boolean"); + + b.Property("AppEUI") + .HasColumnType("text"); + + b.Property("ClassType") + .HasColumnType("integer"); + + b.Property("Deduplication") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Downlink") + .HasColumnType("boolean"); + + b.Property("IsBuiltin") + .HasColumnType("boolean"); + + b.Property("KeepAliveTimeout") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PreferredWindow") + .HasColumnType("integer"); + + b.Property("RXDelay") + .HasColumnType("integer"); + + b.Property("SensorDecoder") + .HasColumnType("text"); + + b.Property("SupportLoRaFeatures") + .HasColumnType("boolean"); + + b.Property("UseOTAA") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DeviceModels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Confirmed") + .HasColumnType("boolean"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Frame") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsBuiltin") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Port") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelCommands"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModelProperty", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsWritable") + .HasColumnType("boolean"); + + b.Property("ModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("PropertyType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelProperties"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTag", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("Searchable") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DeviceTags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DeviceId") + .HasColumnType("text"); + + b.Property("EdgeDeviceId") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("EdgeDeviceId"); + + b.ToTable("DeviceTagValues"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConnectionState") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NbDevices") + .HasColumnType("integer"); + + b.Property("NbModules") + .HasColumnType("integer"); + + b.Property("Scope") + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeviceModelId"); + + b.ToTable("EdgeDevices"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExternalIdentifier") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("EdgeDeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModuleName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModelCommands"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Avatar") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Color") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeviceId") + .HasColumnType("text"); + + b.Property("DeviceModelId") + .HasColumnType("text"); + + b.Property("EdgeDeviceId") + .HasColumnType("text"); + + b.Property("EdgeDeviceModelId") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("DeviceModelId"); + + b.HasIndex("EdgeDeviceId"); + + b.HasIndex("EdgeDeviceModelId"); + + b.ToTable("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LoRaDeviceTelemetry", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("EnqueuedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LorawanDeviceId") + .HasColumnType("text"); + + b.Property("Telemetry") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("LorawanDeviceId"); + + b.ToTable("LoRaDeviceTelemetry"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Principals"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Avatar") + .HasColumnType("text"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("FamilyName") + .HasColumnType("text"); + + b.Property("GivenName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("GivenName") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.HasBaseType("IoTHub.Portal.Domain.Entities.Device"); + + b.Property("ABPRelaxMode") + .HasColumnType("boolean"); + + b.Property("AlreadyLoggedInOnce") + .HasColumnType("boolean"); + + b.Property("AppEUI") + .HasColumnType("text"); + + b.Property("AppKey") + .HasColumnType("text"); + + b.Property("AppSKey") + .HasColumnType("text"); + + b.Property("ClassType") + .HasColumnType("integer"); + + b.Property("DataRate") + .HasColumnType("text"); + + b.Property("Deduplication") + .HasColumnType("integer"); + + b.Property("DevAddr") + .HasColumnType("text"); + + b.Property("Downlink") + .HasColumnType("boolean"); + + b.Property("FCntDownStart") + .HasColumnType("integer"); + + b.Property("FCntResetCounter") + .HasColumnType("integer"); + + b.Property("FCntUpStart") + .HasColumnType("integer"); + + b.Property("GatewayID") + .HasColumnType("text"); + + b.Property("KeepAliveTimeout") + .HasColumnType("integer"); + + b.Property("NbRep") + .HasColumnType("text"); + + b.Property("NwkSKey") + .HasColumnType("text"); + + b.Property("PreferredWindow") + .HasColumnType("integer"); + + b.Property("RX1DROffset") + .HasColumnType("integer"); + + b.Property("RX2DataRate") + .HasColumnType("integer"); + + b.Property("RXDelay") + .HasColumnType("integer"); + + b.Property("ReportedRX1DROffset") + .HasColumnType("text"); + + b.Property("ReportedRX2DataRate") + .HasColumnType("text"); + + b.Property("ReportedRXDelay") + .HasColumnType("text"); + + b.Property("SensorDecoder") + .HasColumnType("text"); + + b.Property("Supports32BitFCnt") + .HasColumnType("boolean"); + + b.Property("TxPower") + .HasColumnType("text"); + + b.Property("UseOTAA") + .HasColumnType("boolean"); + + b.ToTable("LorawanDevices", (string)null); + }); + + modelBuilder.Entity("GroupUser", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Group", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("MembersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithMany("AccessControls") + .HasForeignKey("PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Principal"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Action", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Role", null) + .WithMany("Actions") + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.DeviceModel", "DeviceModel") + .WithMany() + .HasForeignKey("DeviceModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DeviceModel"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithMany("Tags") + .HasForeignKey("DeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDevice", null) + .WithMany("Tags") + .HasForeignKey("EdgeDeviceId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", "DeviceModel") + .WithMany() + .HasForeignKey("DeviceModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DeviceModel"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("Group") + .HasForeignKey("IoTHub.Portal.Domain.Entities.Group", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithMany("Labels") + .HasForeignKey("DeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.DeviceModel", null) + .WithMany("Labels") + .HasForeignKey("DeviceModelId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDevice", null) + .WithMany("Labels") + .HasForeignKey("EdgeDeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", null) + .WithMany("Labels") + .HasForeignKey("EdgeDeviceModelId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LoRaDeviceTelemetry", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.LorawanDevice", null) + .WithMany("Telemetry") + .HasForeignKey("LorawanDeviceId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("User") + .HasForeignKey("IoTHub.Portal.Domain.Entities.User", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithOne() + .HasForeignKey("IoTHub.Portal.Domain.Entities.LorawanDevice", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.Navigation("Labels"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModel", b => + { + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.Navigation("Labels"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", b => + { + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Navigation("AccessControls"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => + { + b.Navigation("Actions"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.Navigation("Telemetry"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IoTHub.Portal.Postgres/Migrations/20240426124632_RoleBasedAccessControl-1_1.cs b/src/IoTHub.Portal.Postgres/Migrations/20240426124632_RoleBasedAccessControl-1_1.cs new file mode 100644 index 000000000..0f7f41fc2 --- /dev/null +++ b/src/IoTHub.Portal.Postgres/Migrations/20240426124632_RoleBasedAccessControl-1_1.cs @@ -0,0 +1,49 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +#nullable disable +namespace IoTHub.Portal.Postgres.Migrations +{ + using Microsoft.EntityFrameworkCore.Migrations; + + /// + public partial class RoleBasedAccessControl1_1 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.CreateIndex( + name: "IX_Users_Email", + table: "Users", + column: "Email", + unique: true); + + _ = migrationBuilder.CreateIndex( + name: "IX_Users_GivenName", + table: "Users", + column: "GivenName", + unique: true); + + _ = migrationBuilder.CreateIndex( + name: "IX_Groups_Name", + table: "Groups", + column: "Name", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.DropIndex( + name: "IX_Users_Email", + table: "Users"); + + _ = migrationBuilder.DropIndex( + name: "IX_Users_GivenName", + table: "Users"); + + _ = migrationBuilder.DropIndex( + name: "IX_Groups_Name", + table: "Groups"); + } + } +} diff --git a/src/IoTHub.Portal.Postgres/Migrations/20240604095400_RoleBasedAccessControl-1_2.Designer.cs b/src/IoTHub.Portal.Postgres/Migrations/20240604095400_RoleBasedAccessControl-1_2.Designer.cs new file mode 100644 index 000000000..9d520d042 --- /dev/null +++ b/src/IoTHub.Portal.Postgres/Migrations/20240604095400_RoleBasedAccessControl-1_2.Designer.cs @@ -0,0 +1,853 @@ +// +using System; +using IoTHub.Portal.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IoTHub.Portal.Postgres.Migrations +{ + [DbContext(typeof(PortalDbContext))] + [Migration("20240604095400_RoleBasedAccessControl-1_2")] + partial class RoleBasedAccessControl1_2 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("GroupUser", b => + { + b.Property("GroupsId") + .HasColumnType("text"); + + b.Property("MembersId") + .HasColumnType("text"); + + b.HasKey("GroupsId", "MembersId"); + + b.HasIndex("MembersId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("PrincipalId"); + + b.HasIndex("RoleId"); + + b.ToTable("AccessControls"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Action", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("Actions"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Concentrator", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientThumbprint") + .HasColumnType("text"); + + b.Property("DeviceType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConnected") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LoraRegion") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Concentrators"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConnected") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("StatusUpdatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeviceModelId"); + + b.ToTable("Devices", (string)null); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ABPRelaxMode") + .HasColumnType("boolean"); + + b.Property("AppEUI") + .HasColumnType("text"); + + b.Property("ClassType") + .HasColumnType("integer"); + + b.Property("Deduplication") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Downlink") + .HasColumnType("boolean"); + + b.Property("IsBuiltin") + .HasColumnType("boolean"); + + b.Property("KeepAliveTimeout") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PreferredWindow") + .HasColumnType("integer"); + + b.Property("RXDelay") + .HasColumnType("integer"); + + b.Property("SensorDecoder") + .HasColumnType("text"); + + b.Property("SupportLoRaFeatures") + .HasColumnType("boolean"); + + b.Property("UseOTAA") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DeviceModels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Confirmed") + .HasColumnType("boolean"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Frame") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsBuiltin") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Port") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelCommands"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModelProperty", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsWritable") + .HasColumnType("boolean"); + + b.Property("ModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("PropertyType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelProperties"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTag", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("Searchable") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DeviceTags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DeviceId") + .HasColumnType("text"); + + b.Property("EdgeDeviceId") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("EdgeDeviceId"); + + b.ToTable("DeviceTagValues"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConnectionState") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NbDevices") + .HasColumnType("integer"); + + b.Property("NbModules") + .HasColumnType("integer"); + + b.Property("Scope") + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DeviceModelId"); + + b.ToTable("EdgeDevices"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExternalIdentifier") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("EdgeDeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModuleName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModelCommands"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Color") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Color") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeviceId") + .HasColumnType("text"); + + b.Property("DeviceModelId") + .HasColumnType("text"); + + b.Property("EdgeDeviceId") + .HasColumnType("text"); + + b.Property("EdgeDeviceModelId") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("DeviceModelId"); + + b.HasIndex("EdgeDeviceId"); + + b.HasIndex("EdgeDeviceModelId"); + + b.ToTable("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LoRaDeviceTelemetry", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("EnqueuedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("LorawanDeviceId") + .HasColumnType("text"); + + b.Property("Telemetry") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("LorawanDeviceId"); + + b.ToTable("LoRaDeviceTelemetry"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Principals"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Color") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Avatar") + .HasColumnType("text"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("FamilyName") + .HasColumnType("text"); + + b.Property("GivenName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("GivenName") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.HasBaseType("IoTHub.Portal.Domain.Entities.Device"); + + b.Property("ABPRelaxMode") + .HasColumnType("boolean"); + + b.Property("AlreadyLoggedInOnce") + .HasColumnType("boolean"); + + b.Property("AppEUI") + .HasColumnType("text"); + + b.Property("AppKey") + .HasColumnType("text"); + + b.Property("AppSKey") + .HasColumnType("text"); + + b.Property("ClassType") + .HasColumnType("integer"); + + b.Property("DataRate") + .HasColumnType("text"); + + b.Property("Deduplication") + .HasColumnType("integer"); + + b.Property("DevAddr") + .HasColumnType("text"); + + b.Property("Downlink") + .HasColumnType("boolean"); + + b.Property("FCntDownStart") + .HasColumnType("integer"); + + b.Property("FCntResetCounter") + .HasColumnType("integer"); + + b.Property("FCntUpStart") + .HasColumnType("integer"); + + b.Property("GatewayID") + .HasColumnType("text"); + + b.Property("KeepAliveTimeout") + .HasColumnType("integer"); + + b.Property("NbRep") + .HasColumnType("text"); + + b.Property("NwkSKey") + .HasColumnType("text"); + + b.Property("PreferredWindow") + .HasColumnType("integer"); + + b.Property("RX1DROffset") + .HasColumnType("integer"); + + b.Property("RX2DataRate") + .HasColumnType("integer"); + + b.Property("RXDelay") + .HasColumnType("integer"); + + b.Property("ReportedRX1DROffset") + .HasColumnType("text"); + + b.Property("ReportedRX2DataRate") + .HasColumnType("text"); + + b.Property("ReportedRXDelay") + .HasColumnType("text"); + + b.Property("SensorDecoder") + .HasColumnType("text"); + + b.Property("Supports32BitFCnt") + .HasColumnType("boolean"); + + b.Property("TxPower") + .HasColumnType("text"); + + b.Property("UseOTAA") + .HasColumnType("boolean"); + + b.ToTable("LorawanDevices", (string)null); + }); + + modelBuilder.Entity("GroupUser", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Group", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("MembersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithMany("AccessControls") + .HasForeignKey("PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Principal"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Action", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Role", null) + .WithMany("Actions") + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.DeviceModel", "DeviceModel") + .WithMany() + .HasForeignKey("DeviceModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DeviceModel"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithMany("Tags") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDevice", null) + .WithMany("Tags") + .HasForeignKey("EdgeDeviceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", "DeviceModel") + .WithMany() + .HasForeignKey("DeviceModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DeviceModel"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("Group") + .HasForeignKey("IoTHub.Portal.Domain.Entities.Group", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithMany("Labels") + .HasForeignKey("DeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.DeviceModel", null) + .WithMany("Labels") + .HasForeignKey("DeviceModelId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDevice", null) + .WithMany("Labels") + .HasForeignKey("EdgeDeviceId"); + + b.HasOne("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", null) + .WithMany("Labels") + .HasForeignKey("EdgeDeviceModelId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LoRaDeviceTelemetry", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.LorawanDevice", null) + .WithMany("Telemetry") + .HasForeignKey("LorawanDeviceId"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("User") + .HasForeignKey("IoTHub.Portal.Domain.Entities.User", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) + .WithOne() + .HasForeignKey("IoTHub.Portal.Domain.Entities.LorawanDevice", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Device", b => + { + b.Navigation("Labels"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.DeviceModel", b => + { + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDevice", b => + { + b.Navigation("Labels"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.EdgeDeviceModel", b => + { + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Navigation("AccessControls"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => + { + b.Navigation("Actions"); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.Navigation("Telemetry"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/IoTHub.Portal.Postgres/Migrations/20240604095400_RoleBasedAccessControl-1_2.cs b/src/IoTHub.Portal.Postgres/Migrations/20240604095400_RoleBasedAccessControl-1_2.cs new file mode 100644 index 000000000..a9f001aec --- /dev/null +++ b/src/IoTHub.Portal.Postgres/Migrations/20240604095400_RoleBasedAccessControl-1_2.cs @@ -0,0 +1,52 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +#nullable disable + +namespace IoTHub.Portal.Postgres.Migrations +{ + using Microsoft.EntityFrameworkCore.Migrations; + + /// + public partial class RoleBasedAccessControl1_2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.DropColumn( + name: "Avatar", + table: "Groups"); + + _ = migrationBuilder.AddColumn( + name: "Color", + table: "Roles", + type: "text", + nullable: false, + defaultValue: ""); + + _ = migrationBuilder.AddColumn( + name: "Color", + table: "Groups", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.DropColumn( + name: "Color", + table: "Roles"); + + _ = migrationBuilder.DropColumn( + name: "Color", + table: "Groups"); + + _ = migrationBuilder.AddColumn( + name: "Avatar", + table: "Groups", + type: "text", + nullable: true); + } + } +} diff --git a/src/IoTHub.Portal.Postgres/Migrations/PortalDbContextModelSnapshot.cs b/src/IoTHub.Portal.Postgres/Migrations/PortalDbContextModelSnapshot.cs index 73e887b02..f461f06f5 100644 --- a/src/IoTHub.Portal.Postgres/Migrations/PortalDbContextModelSnapshot.cs +++ b/src/IoTHub.Portal.Postgres/Migrations/PortalDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using IoTHub.Portal.Infrastructure; using Microsoft.EntityFrameworkCore; @@ -22,12 +22,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("GroupUser", b => + { + b.Property("GroupsId") + .HasColumnType("text"); + + b.Property("MembersId") + .HasColumnType("text"); + + b.HasKey("GroupsId", "MembersId"); + + b.HasIndex("MembersId"); + + b.ToTable("GroupUser"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => { b.Property("Id") .HasColumnType("text"); - b.Property("GroupId") + b.Property("PrincipalId") + .IsRequired() .HasColumnType("text"); b.Property("RoleId") @@ -38,17 +54,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); - b.Property("UserId") - .HasColumnType("text"); - b.HasKey("Id"); - b.HasIndex("GroupId"); + b.HasIndex("PrincipalId"); b.HasIndex("RoleId"); - b.HasIndex("UserId"); - b.ToTable("AccessControls"); }); @@ -388,16 +399,29 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("text"); - b.Property("Avatar") + b.Property("Color") .IsRequired() .HasColumnType("text"); + b.Property("Description") + .HasColumnType("text"); + b.Property("Name") .IsRequired() .HasColumnType("text"); + b.Property("PrincipalId") + .IsRequired() + .HasColumnType("text"); + b.HasKey("Id"); + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("PrincipalId") + .IsUnique(); + b.ToTable("Groups"); }); @@ -461,17 +485,37 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("LoRaDeviceTelemetry"); }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Principals"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => { b.Property("Id") .HasColumnType("text"); + b.Property("Color") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + b.Property("Name") .IsRequired() .HasColumnType("text"); b.HasKey("Id"); + b.HasIndex("Name") + .IsUnique(); + b.ToTable("Roles"); }); @@ -480,39 +524,39 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("text"); + b.Property("Avatar") + .HasColumnType("text"); + b.Property("Email") .IsRequired() .HasColumnType("text"); - b.Property("Forename") - .IsRequired() + b.Property("FamilyName") .HasColumnType("text"); - b.Property("Name") + b.Property("GivenName") .IsRequired() .HasColumnType("text"); - b.HasKey("Id"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.UserMemberShip", b => - { - b.Property("UserId") + b.Property("Name") .HasColumnType("text"); - b.Property("GroupId") + b.Property("PrincipalId") + .IsRequired() .HasColumnType("text"); - b.Property("Id") - .HasColumnType("text"); + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); - b.HasKey("UserId", "GroupId"); + b.HasIndex("GivenName") + .IsUnique(); - b.HasIndex("GroupId"); + b.HasIndex("PrincipalId") + .IsUnique(); - b.ToTable("UserMemberShip"); + b.ToTable("Users"); }); modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => @@ -625,21 +669,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("LorawanDevices", (string)null); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + modelBuilder.Entity("GroupUser", b => { b.HasOne("IoTHub.Portal.Domain.Entities.Group", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IoTHub.Portal.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("MembersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.AccessControl", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") .WithMany("AccessControls") - .HasForeignKey("GroupId"); + .HasForeignKey("PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("IoTHub.Portal.Domain.Entities.Role", "Role") .WithMany() .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) + .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("IoTHub.Portal.Domain.Entities.User", null) - .WithMany("AccessControls") - .HasForeignKey("UserId"); + b.Navigation("Principal"); b.Navigation("Role"); }); @@ -686,6 +745,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DeviceModel"); }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + { + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("Group") + .HasForeignKey("IoTHub.Portal.Domain.Entities.Group", "PrincipalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Principal"); + }); + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Label", b => { b.HasOne("IoTHub.Portal.Domain.Entities.Device", null) @@ -712,23 +782,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("LorawanDeviceId"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.UserMemberShip", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => { - b.HasOne("IoTHub.Portal.Domain.Entities.Group", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IoTHub.Portal.Domain.Entities.User", "User") - .WithMany("Groups") - .HasForeignKey("UserId") + b.HasOne("IoTHub.Portal.Domain.Entities.Principal", "Principal") + .WithOne("User") + .HasForeignKey("IoTHub.Portal.Domain.Entities.User", "PrincipalId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Group"); - - b.Navigation("User"); + b.Navigation("Principal"); }); modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => @@ -764,11 +826,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Labels"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Group", b => + modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Principal", b => { b.Navigation("AccessControls"); - b.Navigation("Members"); + b.Navigation("Group"); + + b.Navigation("User"); }); modelBuilder.Entity("IoTHub.Portal.Domain.Entities.Role", b => @@ -776,13 +840,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Actions"); }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.User", b => - { - b.Navigation("AccessControls"); - - b.Navigation("Groups"); - }); - modelBuilder.Entity("IoTHub.Portal.Domain.Entities.LorawanDevice", b => { b.Navigation("Telemetry"); diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/AccessControlController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/AccessControlController.cs new file mode 100644 index 000000000..ecb13cc2d --- /dev/null +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/AccessControlController.cs @@ -0,0 +1,178 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Server.Controllers.v10 +{ + using System.Threading.Tasks; + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Logging; + using Microsoft.AspNetCore.Mvc.Routing; + using System; + + [Authorize] + [ApiVersion("1.0")] + [Route("api/access-controls")] + [ApiExplorerSettings(GroupName = "Access Control Management")] + [ApiController] + public class AccessControlController : ControllerBase + { + private readonly ILogger logger; + private readonly IAccessControlManagementService service; + + public AccessControlController(ILogger logger, IAccessControlManagementService service) + { + this.logger = logger; + this.service = service; + } + + [HttpGet(Name = "Get Pagined Access Control")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PaginationResult))] + public async Task> Get( + string searchKeyword = null, + int pageSize = 10, + int pageNumber = 0, + [FromQuery] string[] orderBy = null, + [FromQuery] string? principalId = null + ) + { + try + { + var paginedResult = await service.GetAccessControlPage( + searchKeyword, + pageSize, + pageNumber, + orderBy, + principalId + ); + logger.LogInformation("AccessControls fetched successfully. Total AccessControl fetched : {Count}", paginedResult.TotalCount); + var nextPage = string.Empty; + if (paginedResult.HasNextPage) + { + nextPage = Url.RouteUrl(new UrlRouteContext + { + RouteName = "Get Pagined Access Control", + Values = new { searchKeyword, pageSize, pageNumber = pageNumber + 1, orderBy } + }); + } + return new PaginationResult + { + Items = paginedResult.Data, + TotalItems = paginedResult.TotalCount, + NextPage = nextPage + }; + } + catch (Exception ex) + { + logger.LogError(ex, "Error fetching accessControls."); + throw; + } + } + + [HttpGet("{id}", Name = "Get AccessControl By Id")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AccessControlModel))] + public async Task GetACById(string id) + { + try + { + var ac = await service.GetAccessControlAsync(id); + if (ac is null) + { + logger.LogWarning("AccessControl with ID {AcId} not found", id); + return NotFound(); + } + logger.LogInformation("Details retrieved for accessControl with ID {AcId}", id); + return Ok(ac); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to get details for accessControl with ID {AcId}", id); + throw; + } + + } + + /// + /// Create a new role and the associated actions + /// + /// AccessControl model that we want to create in db + /// HTTP Post response + [HttpPost(Name = "POST Create a AccessControl")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task CreateAccessControlAsync(AccessControlModel accessControl) + { + try + { + var result = await this.service.CreateAccessControl(accessControl); + logger.LogInformation("AccessControl created successfully with ID {AcId}", result.Id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError($"Failed to create accessControl. : {ex}"); + return BadRequest(ex); + } + } + + /// + /// Edit an existing role and the associated actions, delete the actions that are not in the new list + /// + /// AccessControl model that we want to update in db + /// Current role name (before any changes) + /// HTTP Put response, updated role + [HttpPut("{id}", Name = "PUT Edit AccessControl")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task EditAccessControlAsync(string id, AccessControlModel accessControl) + { + try + { + var result = await this.service.UpdateAccessControl(id, accessControl); + logger.LogInformation("AccessControl with ID {AcId} updated successfully", id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to update AccessControl with ID {AcId}", id); + throw; + } + } + + + /// + /// Delete a role by name + /// + /// AccessControl id that we want to delete + /// + [HttpDelete("{id}")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteAccessControl(string id) + { + try + { + var result = await service.DeleteAccessControl(id); + if (!result) + { + logger.LogWarning("AccessControl with ID {AcId} not found", id); + return NotFound("AccessControl not found."); + } + logger.LogInformation("AccessControl with ID {AcId} deleted successfully", id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to delete AccessControl with ID {AcId}", id); + throw; + } + } + } +} diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/AdminController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/AdminController.cs index e0fa18871..d1c830f38 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/AdminController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/AdminController.cs @@ -8,7 +8,6 @@ namespace IoTHub.Portal.Server.Controllers.V10 using System.Threading.Tasks; using IoTHub.Portal.Application.Managers; using IoTHub.Portal.Shared.Models.v10; - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -28,7 +27,7 @@ public AdminController(IExportManager exportManager) } [HttpPost("devices/_export", Name = "Export devices")] - [Authorize(Policy = Policies.ExportDevices)] + [AllowAnonymous] public async Task ExportDeviceList() { var stream = new MemoryStream(); @@ -40,7 +39,7 @@ public async Task ExportDeviceList() } [HttpPost("devices/_template", Name = "Download template file")] - [Authorize(Policy = Policies.DownloadDeviceTemplate)] + [AllowAnonymous] public async Task ExportTemplateFile() { var stream = new MemoryStream(); @@ -52,7 +51,7 @@ public async Task ExportTemplateFile() } [HttpPost("devices/_import", Name = "Import devices")] - [Authorize(Policy = Policies.ImportDevices)] + [AllowAnonymous] public async Task> ImportDeviceList(IFormFile file) { using var stream = file.OpenReadStream(); diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/DashboardController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/DashboardController.cs index c2780d6c5..f80bda136 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/DashboardController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/DashboardController.cs @@ -1,12 +1,11 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Server.Controllers.v1._0 +namespace IoTHub.Portal.Server.Controllers.v10 { - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; - using Shared.Models.v1._0; + using Shared.Models.v10; [Authorize] [ApiController] @@ -23,7 +22,7 @@ public DashboardController(PortalMetric portalMetric) } [HttpGet("metrics", Name = "Get Portal Metrics")] - [Authorize(Policy = Policies.GetPortalMetrics)] + [AllowAnonymous] public ActionResult GetPortalMetrics() { return this.portalMetric; diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceConfigurationsController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceConfigurationsController.cs index 039900e06..5d399c879 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceConfigurationsController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceConfigurationsController.cs @@ -11,7 +11,6 @@ namespace IoTHub.Portal.Server.Controllers.v10 using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Shared.Models.v10; - using IoTHub.Portal.Shared.Security; [Authorize] [ApiController] @@ -28,7 +27,7 @@ public DeviceConfigurationsController(IDeviceConfigurationsService deviceConfigu } [HttpGet(Name = "GET Device configurations")] - [Authorize(Policy = Policies.GetAllDeviceConfigurations)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Get() { @@ -36,7 +35,7 @@ public async Task> Get() } [HttpGet("{configurationId}", Name = "GET Device configuration")] - [Authorize(Policy = Policies.GetDeviceConfiguration)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Get(string configurationId) { @@ -44,7 +43,7 @@ public async Task> Get(string configurationId) } [HttpGet("{configurationId}/metrics", Name = "GET Device configuration metrics")] - [Authorize(Policy = Policies.GetAllDeviceConfigurationMetrics)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetConfigurationMetrics(string configurationId) { @@ -52,7 +51,7 @@ public async Task> GetConfigurationMetrics(st } [HttpPost(Name = "POST Create Device configuration")] - [Authorize(Policy = Policies.CreateDeviceConfiguration)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task CreateConfig(DeviceConfig deviceConfig) { @@ -61,6 +60,7 @@ public async Task CreateConfig(DeviceConfig deviceConfig) [HttpPut(Name = "PUT Update Device configuration")] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task UpdateConfig(DeviceConfig deviceConfig) { @@ -68,7 +68,7 @@ public async Task UpdateConfig(DeviceConfig deviceConfig) } [HttpDelete("{configurationId}", Name = "DELETE Device configuration")] - [Authorize(Policy = Policies.DeleteDeviceConfiguration)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task DeleteConfig(string configurationId) { diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceModelPropertiesController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceModelPropertiesController.cs index 1f7c747dc..1ab0b6557 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceModelPropertiesController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceModelPropertiesController.cs @@ -8,7 +8,6 @@ namespace IoTHub.Portal.Server.Controllers.V10 using AutoMapper; using IoTHub.Portal.Application.Services; using IoTHub.Portal.Models.v10; - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -37,7 +36,7 @@ public DeviceModelPropertiesController( /// /// The device model properties [HttpGet(Name = "GET Device model properties")] - [Authorize(Policy = Policies.GetDeviceModelProperties)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status404NotFound)] public override async Task>> GetProperties(string id) @@ -51,7 +50,7 @@ public override async Task>> GetPropert /// The device model properties /// The model properties [HttpPost(Name = "POST Device model properties")] - [Authorize(Policy = Policies.SetDeviceModelProperties)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public override async Task SetProperties(string id, IEnumerable properties) diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceModelsController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceModelsController.cs index c0343f2b3..588ba512b 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceModelsController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceModelsController.cs @@ -10,7 +10,6 @@ namespace IoTHub.Portal.Server.Controllers.V10 using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using IoTHub.Portal.Shared.Models.v10.Filters; - using IoTHub.Portal.Shared.Security; [Authorize] [ApiController] @@ -33,7 +32,7 @@ public DeviceModelsController(IDeviceModelService /// An array representing the device models. [HttpGet(Name = "GET Device model list")] - [Authorize(Policy = Policies.GetAllDeviceModels)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public override async Task>> GetItems([FromQuery] DeviceModelFilter deviceModelFilter) { @@ -46,8 +45,8 @@ public override async Task>> GetIt /// The devic emodel identifier. /// The device model details. [HttpGet("{id}", Name = "GET Device model")] - [Authorize(Policy = Policies.GetDeviceModel)] [ProducesResponseType(StatusCodes.Status200OK)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status404NotFound)] public override Task> GetItem(string id) { @@ -59,7 +58,7 @@ public override Task> GetItem(string id) /// /// The device model identifier [HttpGet("{id}/avatar", Name = "GET Device model avatar URL")] - [Authorize(Policy = Policies.GetDeviceModelAvatar)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public override Task> GetAvatar(string id) @@ -74,7 +73,7 @@ public override Task> GetAvatar(string id) /// The file. /// The avatar. [HttpPost("{id}/avatar", Name = "POST Update the device model avatar")] - [Authorize(Policy = Policies.UpdateDeviceModelAvatar)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public override Task> ChangeAvatar(string id, IFormFile file) @@ -87,7 +86,7 @@ public override Task> ChangeAvatar(string id, IFormFile fil /// /// The model identifier. [HttpDelete("{id}/avatar", Name = "DELETE Remove the device model avatar")] - [Authorize(Policy = Policies.DeleteDeviceModelAvatar)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public override Task DeleteAvatar(string id) @@ -101,7 +100,7 @@ public override Task DeleteAvatar(string id) /// The device model. /// The action result. [HttpPost(Name = "POST Create a new device model")] - [Authorize(Policy = Policies.CreateDeviceModel)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public override Task Post(DeviceModelDto deviceModel) @@ -116,6 +115,7 @@ public override Task Post(DeviceModelDto deviceModel) /// The action result. [HttpPut(Name = "PUT Update the device model")] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -130,7 +130,7 @@ public override Task Put(DeviceModelDto deviceModel) /// The device model identifier. /// The action result. [HttpDelete("{id}", Name = "DELETE Remove the device model")] - [Authorize(Policy = Policies.DeleteDeviceModel)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceTagSettingsController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceTagSettingsController.cs index 0839af1a1..ab3f552a4 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceTagSettingsController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/DeviceTagSettingsController.cs @@ -8,7 +8,6 @@ namespace IoTHub.Portal.Server.Controllers.V10 using System.Threading.Tasks; using IoTHub.Portal.Application.Services; using IoTHub.Portal.Models.v10; - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -50,7 +49,7 @@ public DeviceTagSettingsController( /// List of tags. /// The action result. [HttpPost(Name = "POST Update the Device tags settings")] - [Authorize(Policy = Policies.UpdateDeviceTagSettings)] + [AllowAnonymous] public async Task Post(IEnumerable tags) { ArgumentNullException.ThrowIfNull(tags, nameof(tags)); @@ -64,7 +63,7 @@ public async Task Post(IEnumerable tags) /// /// The list of tags [HttpGet(Name = "GET Device tags settings")] - [Authorize(Policy = Policies.GetAllDeviceTagSettings)] + [AllowAnonymous] public ActionResult> Get() { return Ok(this.deviceTagService.GetAllTags()); @@ -76,7 +75,7 @@ public ActionResult> Get() /// Device Tag /// The action result [HttpPatch(Name = "Create or update a device tag")] - [Authorize(Policy = Policies.CreateOrUpdateDeviceTag)] + [AllowAnonymous] public async Task CreateOrUpdateDeviceTag([FromBody] DeviceTagDto deviceTag) { await this.deviceTagService.CreateOrUpdateDeviceTag(deviceTag); @@ -89,7 +88,7 @@ public async Task CreateOrUpdateDeviceTag([FromBody] DeviceTagDto /// Device Tag Name /// The action result [HttpDelete("{deviceTagName}", Name = "Delete a device tag by name")] - [Authorize(Policy = Policies.DeleteDeviceTagByName)] + [AllowAnonymous] public async Task DeleteDeviceTagByName([FromRoute] string deviceTagName) { await this.deviceTagService.DeleteDeviceTagByName(deviceTagName); diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/DevicesController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/DevicesController.cs index fd6f69e5c..409ec9733 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/DevicesController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/DevicesController.cs @@ -8,7 +8,6 @@ namespace IoTHub.Portal.Server.Controllers.V10 using IoTHub.Portal.Application.Services; using IoTHub.Portal.Models.v10; using IoTHub.Portal.Shared.Models.v10; - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -43,7 +42,7 @@ public DevicesController( /// /// [HttpGet(Name = "GET Device list")] - [Authorize(Policy = Policies.GetAllDevices)] + [AllowAnonymous] public Task> SearchItems( string searchText = null, bool? searchStatus = null, @@ -62,7 +61,7 @@ public Task> SearchItems( /// /// The device identifier. [HttpGet("{deviceID}", Name = "GET Device details")] - [Authorize(Policy = Policies.GetDeviceDetails)] + [AllowAnonymous] public override Task GetItem(string deviceID) { return base.GetItem(deviceID); @@ -73,7 +72,7 @@ public override Task GetItem(string deviceID) /// /// The device. [HttpPost(Name = "POST Create device")] - [Authorize(Policy = Policies.CreateDevice)] + [AllowAnonymous] public override Task CreateDeviceAsync(DeviceDetails device) { return base.CreateDeviceAsync(device); @@ -84,7 +83,7 @@ public override Task CreateDeviceAsync(DeviceDetails device) /// /// The device. [HttpPut(Name = "PUT Update device")] - [Authorize(Policy = Policies.UpdateDevice)] + [AllowAnonymous] public override Task UpdateDeviceAsync(DeviceDetails device) { return base.UpdateDeviceAsync(device); @@ -95,7 +94,7 @@ public override Task UpdateDeviceAsync(DeviceDetails device) /// /// The device identifier. [HttpDelete("{deviceID}", Name = "DELETE Remove device")] - [Authorize(Policy = Policies.DeleteDevice)] + [AllowAnonymous] public override Task Delete(string deviceID) { return base.Delete(deviceID); @@ -106,7 +105,7 @@ public override Task Delete(string deviceID) /// /// The device identifier. [HttpGet("{deviceID}/credentials", Name = "GET Device Credentials")] - [Authorize(Policy = Policies.GetDeviceCredentials)] + [AllowAnonymous] public override Task> GetCredentials(string deviceID) { return base.GetCredentials(deviceID); @@ -117,7 +116,7 @@ public override Task> GetCredentials(string devi /// /// The device identifier. [HttpGet("{deviceID}/properties", Name = "GET Device Properties")] - [Authorize(Policy = Policies.GetDeviceProperties)] + [AllowAnonymous] public async Task> GetProperties(string deviceID) { return await this.devicePropertyService.GetProperties(deviceID); @@ -129,7 +128,7 @@ public async Task> GetProperties(string deviceI /// The device identifier. /// The properties values. [HttpPost("{deviceID}/properties", Name = "POST Device Properties")] - [Authorize(Policy = Policies.CreateDeviceProperties)] + [AllowAnonymous] public async Task>> SetProperties(string deviceID, IEnumerable values) { await this.devicePropertyService.SetProperties(deviceID, values); @@ -138,7 +137,7 @@ public async Task>> SetProperties( } [HttpGet("available-labels", Name = "GET Available Labels on Devices")] - [Authorize(Policy = Policies.GetAllAvailableDeviceLabels)] + [AllowAnonymous] public override Task> GetAvailableLabels() { return base.GetAvailableLabels(); diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/EdgeDevicesController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/EdgeDevicesController.cs index 2a8d6f302..c592f4f2d 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/EdgeDevicesController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/EdgeDevicesController.cs @@ -11,7 +11,6 @@ namespace IoTHub.Portal.Server.Controllers.V10 using IoTHub.Portal.Application.Services; using IoTHub.Portal.Models.v10; using IoTHub.Portal.Shared.Models.v10; - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; @@ -83,7 +82,7 @@ public EdgeDevicesController( /// /// [HttpGet(Name = "GET IoT Edge devices")] - [Authorize(Policy = Policies.GetAllEdgeDevices)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PaginationResult))] public async Task> Get( string searchText = null, @@ -134,7 +133,7 @@ public async Task> Get( /// /// The device identifier. [HttpGet("{deviceId}", Name = "GET IoT Edge device")] - [Authorize(Policy = Policies.GetEdgeDevice)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IoTEdgeDevice))] public async Task Get(string deviceId) { @@ -153,7 +152,7 @@ public async Task Get(string deviceId) /// /// The IoT Edge device. [HttpPost(Name = "POST Create IoT Edge")] - [Authorize(Policy = Policies.CreateEdgeDevice)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task CreateEdgeDeviceAsync(IoTEdgeDevice edgeDevice) @@ -167,6 +166,7 @@ public async Task CreateEdgeDeviceAsync(IoTEdgeDevice edgeDevice) /// The IoT Edge device. [HttpPut(Name = "PUT Update IoT Edge")] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task UpdateDeviceAsync(IoTEdgeDevice edgeDevice) { @@ -178,7 +178,7 @@ public async Task UpdateDeviceAsync(IoTEdgeDevice edgeDevice) /// /// The device identifier. [HttpDelete("{deviceId}", Name = "DELETE Remove IoT Edge")] - [Authorize(Policy = Policies.DeleteEdgeDevice)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task DeleteDeviceAsync(string deviceId) { @@ -193,7 +193,7 @@ public async Task DeleteDeviceAsync(string deviceId) /// /// The device identifier. [HttpGet("{deviceId}/credentials", Name = "GET Device enrollment credentials")] - [Authorize(Policy = Policies.GetEdgeDeviceCredentials)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetCredentials(string deviceId) { @@ -213,7 +213,7 @@ public async Task> GetCredentials(string devi /// /// The device identifier. [HttpGet("{deviceId}/enrollementScript/{templateName}", Name = "GET Device enrollment script URL")] - [Authorize(Policy = Policies.GetEdgeDeviceEnrollmentScriptUrl)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetEnrollementScriptUrl(string deviceId, string templateName) { @@ -241,7 +241,6 @@ public ActionResult GetEnrollementScriptUrl(string deviceId, string temp [AllowAnonymous] [HttpGet("enroll", Name = "GET Device enrollment script")] - [Authorize(Policy = Policies.GetEdgeDeviceEnrollmentScript)] public async Task> GetEnrollementScript([FromQuery] string code) { try @@ -264,7 +263,7 @@ public async Task> GetEnrollementScript([FromQuery] string /// The device identifier. /// Name of the method. [HttpPost("{deviceId}/{moduleName}/{methodName}", Name = "POST Execute module command")] - [Authorize(Policy = Policies.ExecuteEdgeModuleMethod)] + [AllowAnonymous] public async Task ExecuteModuleMethod(string deviceId, string moduleName, string methodName) { return await this.edgeDevicesService.ExecuteModuleMethod(deviceId, moduleName, methodName); @@ -277,7 +276,6 @@ public async Task ExecuteModuleMethod(string deviceId, string moduleN /// Edge module /// [HttpPost("{deviceId}/logs", Name = "Get Edge Device logs")] - [Authorize(Policy = Policies.GetEdgeDeviceLogs)] public async Task> GetEdgeDeviceLogs(string deviceId, IoTEdgeModule edgeModule) { ArgumentNullException.ThrowIfNull(edgeModule, nameof(edgeModule)); @@ -286,7 +284,7 @@ public async Task> GetEdgeDeviceLogs(string device } [HttpGet("available-labels", Name = "GET Available Labels on Edge Devices")] - [Authorize(Policy = Policies.GetAllAvailableEdgeDeviceLabels)] + [AllowAnonymous] public Task> GetAvailableLabels() { return this.edgeDevicesService.GetAvailableLabels(); diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/EdgeModelsController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/EdgeModelsController.cs index a74a59b61..4cec7abc1 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/EdgeModelsController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/EdgeModelsController.cs @@ -9,7 +9,6 @@ namespace IoTHub.Portal.Server.Controllers.v10 using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -29,7 +28,7 @@ public EdgeModelsController( } [HttpGet] - [Authorize(Policy = Policies.GetAllEdgeModel)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetEdgeModelList([FromQuery] EdgeModelFilter edgeModelFilter) { @@ -37,7 +36,7 @@ public async Task>> GetEdgeModelL } [HttpGet("{edgeModelId}")] - [Authorize(Policy = Policies.GetEdgeModel)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetEdgeDeviceModel(string edgeModelId) { @@ -45,7 +44,7 @@ public async Task> GetEdgeDeviceModel(string edgeMode } [HttpPost] - [Authorize(Policy = Policies.CreateEdgeModel)] + [AllowAnonymous] public async Task CreateEdgeModel(IoTEdgeModel EdgeModel) { await this.edgeModelService.CreateEdgeModel(EdgeModel); @@ -54,7 +53,7 @@ public async Task CreateEdgeModel(IoTEdgeModel EdgeModel) } [HttpPut] - [Authorize(Policy = Policies.UpdateEdgeModel)] + [AllowAnonymous] public async Task UpdateEdgeModel(IoTEdgeModel EdgeModel) { await this.edgeModelService.UpdateEdgeModel(EdgeModel); @@ -69,7 +68,7 @@ public async Task UpdateEdgeModel(IoTEdgeModel EdgeModel) /// Http response /// [HttpDelete("{edgeModelId}")] - [Authorize(Policy = Policies.DeleteEdgeModel)] + [AllowAnonymous] public async Task DeleteModelAsync(string edgeModelId) { await this.edgeModelService.DeleteEdgeModel(edgeModelId); @@ -83,7 +82,7 @@ public async Task DeleteModelAsync(string edgeModelId) /// The model identifier. /// The avatar. [HttpGet("{edgeModelId}/avatar", Name = "GET edge Device model avatar URL")] - [Authorize(Policy = Policies.GetEdgeModelAvatar)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public virtual async Task> GetAvatar(string edgeModelId) @@ -98,7 +97,7 @@ public virtual async Task> GetAvatar(string edgeModelId) /// The file. /// The avatar. [HttpPost("{edgeModelId}/avatar", Name = "POST Update the edge device model avatar")] - [Authorize(Policy = Policies.UpdateEdgeModelAvatar)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public virtual async Task> ChangeAvatar(string edgeModelId, IFormFile file) @@ -111,7 +110,7 @@ public virtual async Task> ChangeAvatar(string edgeModelId, /// /// The model identifier. [HttpDelete("{edgeModelId}/avatar", Name = "DELETE Remove the edge device model avatar")] - [Authorize(Policy = Policies.DeleteEdgeModelAvatar)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public virtual async Task DeleteAvatar(string edgeModelId) @@ -125,7 +124,7 @@ public virtual async Task DeleteAvatar(string edgeModelId) /// /// Public edge modules [HttpGet("public-modules", Name = "GET edge public modules")] - [Authorize(Policy = Policies.GetPublicEdgeModules)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public virtual async Task>> GetPublicEdgeModules() diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/GroupsController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/GroupsController.cs new file mode 100644 index 000000000..c3c980e0e --- /dev/null +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/GroupsController.cs @@ -0,0 +1,198 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +namespace IoTHub.Portal.Server.Controllers.V10 +{ + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using System.Collections.Generic; + using System.Threading.Tasks; + using IoTHub.Portal.Application.Services; + using Microsoft.AspNetCore.Http; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.AspNetCore.Mvc.Routing; + using Microsoft.Extensions.Logging; + using System; + + [Authorize] + [ApiController] + [ApiVersion("1.0")] + [Route("api/groups")] + [ApiExplorerSettings(GroupName = "Group Management")] + public class GroupsController : ControllerBase + { + private readonly IGroupManagementService groupManagementService; + private readonly IAccessControlManagementService accessControlService; + private readonly ILogger logger; + + public GroupsController(IGroupManagementService groupManagementService, ILogger logger, IAccessControlManagementService accessControlService) + { + this.groupManagementService = groupManagementService; + this.logger = logger; + this.accessControlService = accessControlService; + } + + [HttpGet] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + public async Task> Get( + string searchKeyword = null, + int pageSize = 10, + int pageNumber = 0, + [FromQuery] string[] orderBy = null + ) + { + try + { + var paginedResult = await groupManagementService.GetGroupPage( + searchKeyword, + pageSize, + pageNumber, + orderBy + ); + logger.LogInformation("Groups fetched successfully. Total groups fetched: {Count}", paginedResult.TotalCount); + var nextPage = string.Empty; + if (paginedResult.HasNextPage) + { + nextPage = Url.RouteUrl(new UrlRouteContext + { + RouteName = "Get Groups", + Values = new { searchKeyword, pageSize, pageNumber = pageNumber + 1, orderBy } + }); + } + return new PaginationResult + { + Items = paginedResult.Data, + TotalItems = paginedResult.TotalCount, + NextPage = nextPage + }; + } + catch (Exception ex) + { + logger.LogError(ex, "Error fetching groups."); + throw; + } + } + + [HttpGet("{id}")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(GroupDetailsModel))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetGroupDetails(string id) + { + try + { + var groupDetails = await groupManagementService.GetGroupDetailsAsync(id); + if (groupDetails == null) + { + logger.LogWarning("Group with ID {GroupId} not found", id); + return NotFound(); + } + var accessControls = await accessControlService.GetAccessControlPage(null,100, 0,null, groupDetails.PrincipalId); + if (accessControls.Data is not null) + { + foreach (var ac in accessControls.Data) + { + groupDetails.AccessControls.Add(ac); + } + } + logger.LogInformation("Details retrieved for group with ID {GroupId}", id); + return Ok(groupDetails); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to get details for group with ID {GroupId}", id); + throw; + } + } + + [HttpPost(Name = "POST Create a Group")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(GroupDetailsModel))] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task CreateGroup([FromBody] GroupDetailsModel group) + { + try + { + var result = await this.groupManagementService.CreateGroupAsync(group); + logger.LogInformation("Group created successfully with ID {GroupId}", result.Id); + return CreatedAtAction(nameof(GetGroupDetails), new { id = result.Id }, result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to create group."); + return BadRequest(); + } + } + + + [HttpPut("{id}", Name = "PUT Edit a Group")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task EditGroupAsync(string id, GroupDetailsModel group) + { + try + { + var result = await this.groupManagementService.UpdateGroup(id, group); + logger.LogInformation("Group with ID {GroupId} updated successfully", id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to update group with ID {GroupId}", id); + throw; + } + } + + /// + /// Delete a group by id + /// + /// Group id that we want to delete + /// + [HttpDelete("{id}")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteGroup(string id) + { + try + { + var result =await groupManagementService.DeleteGroup(id); + if (!result) + { + logger.LogWarning("Group with ID {GroupId} not found", id); + return NotFound("Group not found."); + } + logger.LogInformation("Group with ID {GroupId} deleted successfully", id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to delete group with ID {GroupId}", id); + throw; + } + } + + // Ajouter un utilisateur à un groupe + [HttpPost("{groupId}/members/{userId}")] + [AllowAnonymous] + public async Task AddUserToGroup(string groupId, string userId) + { + var result = await groupManagementService.AddUserToGroup(groupId, userId); + if (result) + return Ok(result); + else + return NotFound(); + } + + [HttpDelete("{groupId}/members/{userId}")] + [AllowAnonymous] + public async Task RemoveUserFromGroup(string groupId, string userId) + { + var result = await groupManagementService.RemoveUserFromGroup(groupId, userId); + if (result) + return Ok(result); + else + return NotFound(); + } + } +} diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/IdeasController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/IdeasController.cs index 40eb316b8..105958afc 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/IdeasController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/IdeasController.cs @@ -1,14 +1,13 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Server.Controllers.v1._0 +namespace IoTHub.Portal.Server.Controllers.v10 { using System.Threading.Tasks; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; - using Shared.Models.v1._0; + using Shared.Models.v10; [Authorize] [ApiController] @@ -25,7 +24,7 @@ public IdeasController(IIdeaService ideasService) } [HttpPost(Name = "Submit Idea to Iot Hub Portal community")] - [Authorize(Policy = Policies.SumitIdea)] + [AllowAnonymous] public Task SubmitIdea([FromBody] IdeaRequest ideaRequest) { return this.ideasService.SubmitIdea(ideaRequest, Request.Headers.UserAgent.ToString()); diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANCommandsController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANCommandsController.cs index 8d8220244..f72846369 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANCommandsController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANCommandsController.cs @@ -8,7 +8,6 @@ namespace IoTHub.Portal.Server.Controllers.V10.LoRaWAN using IoTHub.Portal.Application.Services; using IoTHub.Portal.Models.v10.LoRaWAN; using IoTHub.Portal.Server.Filters; - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -42,7 +41,7 @@ public LoRaWANCommandsController(ILoRaWANCommandService loRaWANCommandService) /// The commands. /// The action result. [HttpPost(Name = "POST Set device model commands")] - [Authorize(Policy = Policies.UpdateLorawanDeviceModelCommands)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task Post(string id, DeviceModelCommandDto[] commands) { @@ -60,7 +59,7 @@ public async Task Post(string id, DeviceModelCommandDto[] command /// The model identifier. /// The action result. [HttpGet(Name = "GET Device model commands")] - [Authorize(Policy = Policies.GetLorawanDeviceModelCommands)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Get(string id) { diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsController.cs index 8cf4ea8d1..41111d79b 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsController.cs @@ -15,7 +15,6 @@ namespace IoTHub.Portal.Server.Controllers.V10.LoRaWAN using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.Logging; - using IoTHub.Portal.Shared.Security; [Authorize] [ApiController] @@ -52,7 +51,7 @@ public LoRaWANConcentratorsController( /// Gets all device concentrators. /// [HttpGet(Name = "GET LoRaWAN Concentrator list")] - [Authorize(Policy = Policies.GetAllConcentrators)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetAllDeviceConcentrator([FromQuery] ConcentratorFilter concentratorFilter) { @@ -87,7 +86,7 @@ public async Task>> GetAllDeviceC /// /// The device identifier. [HttpGet("{deviceId}", Name = "GET LoRaWAN Concentrator")] - [Authorize(Policy = Policies.GetConcentrator)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetDeviceConcentrator(string deviceId) { @@ -99,7 +98,7 @@ public async Task> GetDeviceConcentrator(string de /// /// The device. [HttpPost(Name = "POST Create LoRaWAN concentrator")] - [Authorize(Policy = Policies.CreateConcentrator)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task CreateDeviceAsync(ConcentratorDto device) @@ -126,7 +125,7 @@ public async Task CreateDeviceAsync(ConcentratorDto device) /// /// The device. [HttpPut(Name = "PUT Update LoRaWAN concentrator")] - [Authorize(Policy = Policies.UpdateConcentrator)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task UpdateDeviceAsync(ConcentratorDto device) @@ -153,7 +152,7 @@ public async Task UpdateDeviceAsync(ConcentratorDto device) /// /// The device identifier. [HttpDelete("{deviceId}", Name = "DELETE Remove LoRaWAN concentrator")] - [Authorize(Policy = Policies.DeleteConcentrator)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public async Task Delete(string deviceId) { diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsController.cs index 213b37c17..5aa33b051 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsController.cs @@ -13,7 +13,6 @@ namespace IoTHub.Portal.Server.Controllers.V10.LoRaWAN using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; - using IoTHub.Portal.Shared.Security; [Authorize] [ApiController] @@ -40,7 +39,7 @@ public LoRaWANDeviceModelsController(IDeviceModelService /// An array representing the device models. [HttpGet(Name = "GET LoRaWAN device model list")] - [Authorize(Policy = Policies.GetAllLorawanDeviceModels)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] public override async Task>> GetItems([FromQuery] DeviceModelFilter deviceModelFilter) { @@ -55,7 +54,7 @@ public override async Task>> GetIt /// The devic emodel identifier. /// The device model details. [HttpGet("{id}", Name = "GET LoRaWAN device model")] - [Authorize(Policy = Policies.GetLorawanDeviceModel)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public override Task> GetItem(string id) @@ -68,7 +67,7 @@ public override Task> GetItem(string id) /// /// The device model identifier [HttpGet("{id}/avatar", Name = "GET LoRaWAN device model avatar URL")] - [Authorize(Policy = Policies.GetLorawanDeviceModelAvatar)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public override Task> GetAvatar(string id) @@ -83,7 +82,7 @@ public override Task> GetAvatar(string id) /// The file. /// The avatar. [HttpPost("{id}/avatar", Name = "POST Update the LoRaWAN device model avatar")] - [Authorize(Policy = Policies.UpdateLorawanDeviceModelAvatar)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public override Task> ChangeAvatar(string id, IFormFile file) @@ -96,7 +95,7 @@ public override Task> ChangeAvatar(string id, IFormFile fil /// /// The model identifier. [HttpDelete("{id}/avatar", Name = "DELETE Remove the LoRaWAN device model avatar")] - [Authorize(Policy = Policies.DeleteLorawanDeviceModelAvatar)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public override Task DeleteAvatar(string id) @@ -110,7 +109,7 @@ public override Task DeleteAvatar(string id) /// The device model. /// The action result. [HttpPost(Name = "POST Create a new LoRaWAN device model")] - [Authorize(Policy = Policies.CreateLorawanDeviceModel)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public override Task Post(LoRaDeviceModelDto deviceModelDto) @@ -124,6 +123,7 @@ public override Task Post(LoRaDeviceModelDto deviceModelDto) /// The device model. /// The action result. [HttpPut(Name = "PUT Update the LoRaWAN device model")] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -138,7 +138,7 @@ public override Task Put(LoRaDeviceModelDto deviceModelDto) /// The device model identifier. /// The action result. [HttpDelete("{id}", Name = "DELETE Remove the LoRaWAN device model")] - [Authorize(Policy = Policies.DeleteLorawanDeviceModel)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesController.cs index 52fb8f1e3..7d07da3c3 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesController.cs @@ -6,7 +6,6 @@ namespace IoTHub.Portal.Server.Controllers.V10 using System.Collections.Generic; using System.Threading.Tasks; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Shared.Models.v1._0; using IoTHub.Portal.Shared.Models.v10; using Filters; using Microsoft.AspNetCore.Authorization; @@ -14,7 +13,6 @@ namespace IoTHub.Portal.Server.Controllers.V10 using Microsoft.Extensions.Logging; using Models.v10; using Models.v10.LoRaWAN; - using IoTHub.Portal.Shared.Security; [Authorize] [ApiController] @@ -51,7 +49,7 @@ public LoRaWANDevicesController( /// /// [HttpGet(Name = "GET LoRaWAN device list")] - [Authorize(Policy = Policies.GetAllLorawanDevices)] + [AllowAnonymous] public Task> SearchItems( string searchText = null, bool? searchStatus = null, @@ -69,7 +67,7 @@ public Task> SearchItems( /// /// The device identifier. [HttpGet("{deviceID}", Name = "GET LoRaWAN device details")] - [Authorize(Policy = Policies.GetLorawanDevice)] + [AllowAnonymous] public override Task GetItem(string deviceID) { return base.GetItem(deviceID); @@ -80,7 +78,7 @@ public override Task GetItem(string deviceID) /// /// The device. [HttpPost(Name = "POST Create LoRaWAN device")] - [Authorize(Policy = Policies.CreateLorawanDevice)] + [AllowAnonymous] public override Task CreateDeviceAsync(LoRaDeviceDetails device) { return base.CreateDeviceAsync(device); @@ -91,7 +89,7 @@ public override Task CreateDeviceAsync(LoRaDeviceDetails device) /// /// The device. [HttpPut(Name = "PUT Update LoRaWAN device")] - [Authorize(Policy = Policies.UpdateLorawanDevice)] + [AllowAnonymous] public override Task UpdateDeviceAsync(LoRaDeviceDetails device) { return base.UpdateDeviceAsync(device); @@ -102,7 +100,7 @@ public override Task UpdateDeviceAsync(LoRaDeviceDetails device) /// /// The device identifier. [HttpDelete("{deviceID}", Name = "DELETE Remove LoRaWAN device")] - [Authorize(Policy = Policies.DeleteLorawanDevice)] + [AllowAnonymous] public override Task Delete(string deviceID) { return base.Delete(deviceID); @@ -115,7 +113,7 @@ public override Task Delete(string deviceID) /// The command identifier. /// Incorrect port or invalid DevEui Format. [HttpPost("{deviceId}/_command/{commandId}", Name = "POST Execute LoRaWAN command")] - [Authorize(Policy = Policies.ExecuteLorawanDeviceCommand)] + [AllowAnonymous] public async Task ExecuteCommand(string deviceId, string commandId) { await this.loRaWanCommandService.ExecuteLoRaWANCommand(deviceId, commandId); @@ -130,21 +128,21 @@ public override Task> GetCredentials(string devi } [HttpGet("gateways", Name = "Get Gateways")] - [Authorize(Policy = Policies.GetLorawanDeviceGateways)] + [AllowAnonymous] public ActionResult GetGateways() { return Ok(this.gatewayIdList); } [HttpGet("{deviceId}/telemetry")] - [Authorize(Policy = Policies.GetLorwanDeviceTelemetry)] + [AllowAnonymous] public Task> GetDeviceTelemetry(string deviceId) { return this.deviceService.GetDeviceTelemetry(deviceId); } [HttpGet("available-labels", Name = "GET Available Labels on LoRaWAN Devices")] - [Authorize(Policy = Policies.GetAvailableLorawanDeviceLabels)] + [AllowAnonymous] public override Task> GetAvailableLabels() { return base.GetAvailableLabels(); diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANFrequencyPlansController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANFrequencyPlansController.cs index 24d17a219..5272955f4 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANFrequencyPlansController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/LoRaWAN/LoRaWANFrequencyPlansController.cs @@ -6,7 +6,6 @@ namespace IoTHub.Portal.Server.Controllers.V10.LoRaWAN using System.Collections.Generic; using IoTHub.Portal.Server.Filters; using IoTHub.Portal.Shared.Models.v10.LoRaWAN; - using IoTHub.Portal.Shared.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -23,8 +22,8 @@ public class LoRaWANFrequencyPlansController : ControllerBase /// Get LoRaWAN supported frequency plans. /// [HttpGet(Name = "GET LoRaWAN Frequency plans")] - [Authorize(Policy = Policies.GetFrequencyPlans)] [ProducesResponseType(StatusCodes.Status200OK)] + [AllowAnonymous] public ActionResult> GetFrequencyPlans() { return this.Ok(new[] { diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/RolesController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/RolesController.cs new file mode 100644 index 000000000..4f39dc9c4 --- /dev/null +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/RolesController.cs @@ -0,0 +1,199 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +namespace IoTHub.Portal.Server.Controllers.V10 +{ + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using System.Threading.Tasks; + using IoTHub.Portal.Application.Services; + using Microsoft.AspNetCore.Http; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.Extensions.Logging; + using Microsoft.AspNetCore.Mvc.Routing; + using System; + + [Authorize] + [ApiVersion("1.0")] + [Route("api/roles")] + [ApiExplorerSettings(GroupName = "Role Management")] + [ApiController] + public class RolesController : ControllerBase + { + /// + /// The Role Controller logger : + /// + private readonly ILogger logger; + + /// + /// The Role Management Service : + /// + private readonly IRoleManagementService roleManagementService; + + /// + /// Constructor : Initialize an instance of the . + /// + /// The logger + /// The Role Management Service + public RolesController(IRoleManagementService roleManagementService, ILogger logger) + { + this.roleManagementService = roleManagementService; + this.logger = logger; + } + + /// + /// Get all roles + /// + /// Role name + /// Number of role per page + /// page number + /// Critera order + [HttpGet(Name = "GetRoles")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PaginationResult))] + public async Task> Get( + [FromQuery] string searchKeyword = null, + [FromQuery] int pageSize = 10, + [FromQuery] int pageNumber = 0, + [FromQuery] string[] orderBy = null + ) + { + try + { + var paginedResult = await roleManagementService.GetRolePage( + searchKeyword, + pageSize, + pageNumber, + orderBy + ); + logger.LogInformation("Roles fetched successfully. Total Role fetched : {Count}", paginedResult.TotalCount); + var nextPage = string.Empty; + if (paginedResult.HasNextPage) + { + nextPage = Url.RouteUrl(new UrlRouteContext + { + RouteName = "GetRoles", + Values = new { searchKeyword, pageSize, pageNumber = pageNumber + 1, orderBy }, + }); + } + return new PaginationResult + { + Items = paginedResult.Data, + TotalItems = paginedResult.TotalCount, + NextPage = nextPage, + }; + } + catch (Exception ex) + { + logger.LogError($"Error fetching roles. : {ex}"); + throw; + // Rethrowing the exception (preserves the stack trace) + } + } + + /// + /// Get role details + /// + /// Role id + /// HTTP Get response + [HttpGet("{id}", Name = "GetRole")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RoleDetailsModel))] + public async Task GetRoleDetails(string id) + { + try + { + var role = await roleManagementService.GetRoleDetailsAsync(id); + if (role == null) + { + logger.LogWarning("Role with ID {RoleId} not found", id); + return NotFound(); + } + logger.LogInformation("Details retrieved for role with ID {RoleId}", id); + return Ok(role); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to get details for role with ID {RoleId}", id); + throw; + } + } + + /// + /// Create a new role and the associated actions + /// + /// Role details + /// HTTP Post response + [HttpPost(Name = "POST Create a Role")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(RoleDetailsModel))] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task CreateRoleAsync(RoleDetailsModel role) + { + try + { + var result = await this.roleManagementService.CreateRole(role); + logger.LogInformation("Role created successfully with ID {RoleId}", result.Id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError($"Failed to create role. : {ex}"); + return BadRequest(ex); + } + } + + /// + /// Edit an existing role and the associated actions, delete the actions that are not in the new list + /// + /// Role details + /// Role id + /// HTTP Put response, updated role + [HttpPut("{id}", Name = "PUT Edit Role")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task EditRoleAsync(string id, RoleDetailsModel roleDetails) + { + try + { + var result = await this.roleManagementService.UpdateRole(id, roleDetails); + logger.LogInformation("Role with ID {RoleId} updated successfully", id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to update role with ID {RoleId}", id); + throw; + } + } + + + /// + /// Delete a role by name + /// + /// Role id that we want to delete + /// + [HttpDelete("{id}", Name = "DELETE Role")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteRole(string id) + { + try + { + var result = await roleManagementService.DeleteRole(id); + if (!result) + { + logger.LogWarning("Role with ID {RoleId} not found", id); + return NotFound("Role not found."); + } + logger.LogInformation("Role with ID {RoleId} deleted successfully", id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to delete role with ID {RoleId}", id); + throw; + } + } + } +} diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/SettingsController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/SettingsController.cs index 2c67b3f03..24594ccee 100644 --- a/src/IoTHub.Portal.Server/Controllers/v1.0/SettingsController.cs +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/SettingsController.cs @@ -13,7 +13,6 @@ namespace IoTHub.Portal.Server.Controllers.V10 using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using IoTHub.Portal.Domain; - using IoTHub.Portal.Shared.Security; [ApiController] [AllowAnonymous] @@ -51,7 +50,7 @@ public SettingsController(IOptions configuration, Con /// Returns the OIDC settings. /// Internal server error. [HttpGet("oidc", Name = "GET Open ID settings")] - //[Authorize(Policy = Policies.GetOIDCSettings)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public IActionResult GetOIDCSettings() @@ -63,7 +62,7 @@ public IActionResult GetOIDCSettings() /// Get the portal settings. /// [HttpGet("portal", Name = "GET Portal settings")] - //[Authorize(Policy = Policies.GetPortalSettings)] + [AllowAnonymous] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PortalSettings))] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public IActionResult GetPortalSetting() diff --git a/src/IoTHub.Portal.Server/Controllers/v1.0/UsersController.cs b/src/IoTHub.Portal.Server/Controllers/v1.0/UsersController.cs new file mode 100644 index 000000000..a0314bc23 --- /dev/null +++ b/src/IoTHub.Portal.Server/Controllers/v1.0/UsersController.cs @@ -0,0 +1,178 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +namespace IoTHub.Portal.Server.Controllers.V10 +{ + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using System.Collections.Generic; + using System.Threading.Tasks; + using IoTHub.Portal.Application.Services; + using Microsoft.AspNetCore.Http; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.AspNetCore.Mvc.Routing; + using Microsoft.Extensions.Logging; + using System; + + [Authorize] + [ApiController] + [ApiVersion("1.0")] + [Route("api/users")] + [ApiExplorerSettings(GroupName = "User Management")] + public class UsersController : ControllerBase + { + private readonly IUserManagementService userManagementService; + private readonly IAccessControlManagementService accessControlService; + + private readonly ILogger logger; + + public UsersController(IUserManagementService userManagementService, ILogger logger, IAccessControlManagementService accessControlService) + { + this.userManagementService = userManagementService; + this.logger = logger; + this.accessControlService = accessControlService; + } + + [HttpGet(Name = "Get Users")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + public async Task> Get( + [FromQuery] string searchName = null, + [FromQuery] string searchEmail = null, + [FromQuery] int pageSize = 10, + [FromQuery] int pageNumber = 0, + [FromQuery] string[] orderBy = null + ) + { + try + { + var paginedResult = await userManagementService.GetUserPage( + searchName, + searchEmail, + pageSize, + pageNumber, + orderBy + ); + logger.LogInformation("Users fetched successfully. Total users fetched: {Count}", paginedResult.TotalCount); + var nextPage = string.Empty; + if (paginedResult.HasNextPage) + { + nextPage = Url.RouteUrl(new UrlRouteContext + { + RouteName = "Get Users", + Values = new { searchName, searchEmail, pageSize, pageNumber = pageNumber + 1, orderBy } + }); + } + return new PaginationResult + { + Items = paginedResult.Data, + TotalItems = paginedResult.TotalCount, + NextPage = nextPage + }; + } + catch (Exception ex) + { + logger.LogError(ex, "Error fetching users."); + throw; + } + } + + [HttpGet("{id}")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserDetailsModel))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetUserDetails(string id) + { + try + { + var userDetails = await userManagementService.GetUserDetailsAsync(id); + if (userDetails == null) + { + logger.LogWarning("User with ID {UserId} not found", id); + return NotFound(); + } + var accessControls = await accessControlService.GetAccessControlPage(null,100, 0,null, userDetails.PrincipalId); + if (accessControls.Data is not null) + { + foreach (var ac in accessControls.Data) + { + userDetails.AccessControls.Add(ac); + } + } + logger.LogInformation("Details retrieved for user with ID {UserId}", id); + return Ok(userDetails); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to get details for user with ID {UserId}", id); + throw; + } + } + + [HttpPost(Name = "POST Create an User")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(UserDetailsModel))] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task CreateUser([FromBody] UserDetailsModel user) + { + try + { + var result = await this.userManagementService.CreateUserAsync(user); + logger.LogInformation("User created successfully with ID {UserId}", result.Id); + return CreatedAtAction(nameof(GetUserDetails), new { id = result.Id }, result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to create user."); + return BadRequest(); + } + } + + [HttpPut("{id}", Name = "PUT Edit User")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task EditUserAsync(string id, UserDetailsModel user) + { + try + { + var result = await this.userManagementService.UpdateUser(id, user); + logger.LogInformation("User with ID {UserId} updated successfully", id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to update user with ID {UserId}", id); + throw; + } + } + + + /// + /// Delete an user by id + /// + /// User id that we want to delete + /// + [HttpDelete("{id}")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteUser(string id) + { + try + { + var result = await userManagementService.DeleteUser(id); + if (!result) + { + logger.LogWarning("User with ID {UserId} not found", id); + return NotFound("User not found."); + } + logger.LogInformation("User with ID {UserId} deleted successfully", id); + return Ok(result); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to delete user with ID {UserId}", id); + throw; + } + } + } +} diff --git a/src/IoTHub.Portal.Server/Services/DeviceModelService.cs b/src/IoTHub.Portal.Server/Services/DeviceModelService.cs index c2a05b001..589accc79 100644 --- a/src/IoTHub.Portal.Server/Services/DeviceModelService.cs +++ b/src/IoTHub.Portal.Server/Services/DeviceModelService.cs @@ -14,10 +14,9 @@ namespace IoTHub.Portal.Server.Services using IoTHub.Portal.Application.Providers; using IoTHub.Portal.Application.Services; using IoTHub.Portal.Infrastructure.Mappers; - using IoTHub.Portal.Infrastructure.Repositories; using IoTHub.Portal.Models.v10; using IoTHub.Portal.Shared.Models; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using Domain; using Domain.Entities; @@ -26,6 +25,7 @@ namespace IoTHub.Portal.Server.Services using Microsoft.AspNetCore.Http; using Microsoft.Azure.Devices.Shared; using Microsoft.EntityFrameworkCore; + using IoTHub.Portal.Crosscutting; public class DeviceModelService : IDeviceModelService where TListItem : class, IDeviceModel diff --git a/src/IoTHub.Portal.Server/Services/IdeaService.cs b/src/IoTHub.Portal.Server/Services/IdeaService.cs index 34d6f919b..694edcb4a 100644 --- a/src/IoTHub.Portal.Server/Services/IdeaService.cs +++ b/src/IoTHub.Portal.Server/Services/IdeaService.cs @@ -15,7 +15,7 @@ namespace IoTHub.Portal.Server.Services using IoTHub.Portal.Domain.Exceptions; using Microsoft.Extensions.Logging; using Newtonsoft.Json; - using Shared.Models.v1._0; + using Shared.Models.v10; using UAParser; public class IdeaService : IIdeaService diff --git a/src/IoTHub.Portal.Server/Services/LoRaWANConcentratorService.cs b/src/IoTHub.Portal.Server/Services/LoRaWANConcentratorService.cs index 109e5244e..64881d4a8 100644 --- a/src/IoTHub.Portal.Server/Services/LoRaWANConcentratorService.cs +++ b/src/IoTHub.Portal.Server/Services/LoRaWANConcentratorService.cs @@ -7,7 +7,6 @@ namespace IoTHub.Portal.Server.Services using AutoMapper; using IoTHub.Portal.Application.Mappers; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Infrastructure.Repositories; using IoTHub.Portal.Shared.Models.v10.Filters; using Domain; using Domain.Entities; @@ -15,7 +14,8 @@ namespace IoTHub.Portal.Server.Services using Domain.Repositories; using Microsoft.Azure.Devices; using Models.v10.LoRaWAN; - using Shared.Models.v1._0; + using Shared.Models.v10; + using IoTHub.Portal.Crosscutting; public class LoRaWANConcentratorService : ILoRaWANConcentratorService { diff --git a/src/IoTHub.Portal.Server/Startup.cs b/src/IoTHub.Portal.Server/Startup.cs index c100bdd76..8da0f7eb1 100644 --- a/src/IoTHub.Portal.Server/Startup.cs +++ b/src/IoTHub.Portal.Server/Startup.cs @@ -41,7 +41,7 @@ namespace IoTHub.Portal.Server using Quartz; using Quartz.Impl.AdoJobStore.Common; using Services; - using Shared.Models.v1._0; + using Shared.Models.v10; public class Startup { @@ -311,6 +311,13 @@ Specify the authorization token got from your IDP as a header. .PersistKeysToDbContext(); } + private static void ConfigureServicesRBAC(IServiceCollection services) + { + _ = services.AddTransient(); + _ = services.AddTransient(); + _ = services.AddTransient(); + _ = services.AddTransient(); + } private static void ConfigureServicesAzure(IServiceCollection services) { _ = services.AddTransient(); @@ -372,6 +379,8 @@ private static void AddAuthenticationAndAuthorization(IServiceCollection service opts.TokenValidationParameters.ValidateActor = configuration.OIDCValidateActor; opts.TokenValidationParameters.ValidateTokenReplay = configuration.OIDCValidateTokenReplay; }); + ConfigureServicesRBAC(services); + } /// diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/AccessControlModel.cs b/src/IoTHub.Portal.Shared/Models/v1.0/AccessControlModel.cs new file mode 100644 index 000000000..86dc8b8b0 --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/AccessControlModel.cs @@ -0,0 +1,15 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10 +{ + + public class AccessControlModel + { + + public string Id { get; set; } + public string PrincipalId { get; set; } + public string Scope { get; set; } = default!; + public RoleModel Role { get; set; } = default!; + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/Filters/AccessControlFilter.cs b/src/IoTHub.Portal.Shared/Models/v1.0/Filters/AccessControlFilter.cs new file mode 100644 index 000000000..cd2d49182 --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/Filters/AccessControlFilter.cs @@ -0,0 +1,12 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10.Filters +{ + + public class AccessControlFilter : PaginationFilter + { + public string Keyword { get; set; } = default!; + + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/Filters/GroupFilter.cs b/src/IoTHub.Portal.Shared/Models/v1.0/Filters/GroupFilter.cs new file mode 100644 index 000000000..bdc4d6780 --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/Filters/GroupFilter.cs @@ -0,0 +1,12 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10.Filters +{ + + public class GroupFilter : PaginationFilter + { + public string Keyword { get; set; } = default!; + + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/Filters/RoleFilter.cs b/src/IoTHub.Portal.Shared/Models/v1.0/Filters/RoleFilter.cs new file mode 100644 index 000000000..3f66e9ba2 --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/Filters/RoleFilter.cs @@ -0,0 +1,12 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10.Filters +{ + + public class RoleFilter : PaginationFilter + { + public string Keyword { get; set; } = default!; + + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/Filters/UserFilter.cs b/src/IoTHub.Portal.Shared/Models/v1.0/Filters/UserFilter.cs new file mode 100644 index 000000000..736c51ef4 --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/Filters/UserFilter.cs @@ -0,0 +1,12 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10.Filters +{ + + public class UserFilter : PaginationFilter + { + public string SearchName { get; set; } = default!; + public string SearchEmail { get; set; } = default!; + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/GroupDetailsModel.cs b/src/IoTHub.Portal.Shared/Models/v1.0/GroupDetailsModel.cs new file mode 100644 index 000000000..04708bd9d --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/GroupDetailsModel.cs @@ -0,0 +1,18 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10 +{ + using System.Collections.Generic; + + public class GroupDetailsModel + { + public string Id { get; set; } + public string Name { get; set; } + public string Color { get; set; } + public string Description { get; set; } + public string PrincipalId { get; set; } + public ICollection Members { get; set; } = new List(); + public ICollection AccessControls { get; set; } = new List(); + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/GroupModel.cs b/src/IoTHub.Portal.Shared/Models/v1.0/GroupModel.cs new file mode 100644 index 000000000..4ea433417 --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/GroupModel.cs @@ -0,0 +1,14 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10 +{ + + public class GroupModel + { + public string Id { get; set; } + public string Name { get; set; } + public string Color { get; set; } + public string PrincipalId { get; set; } + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/IdeaRequest.cs b/src/IoTHub.Portal.Shared/Models/v1.0/IdeaRequest.cs index 53948c251..918128c59 100644 --- a/src/IoTHub.Portal.Shared/Models/v1.0/IdeaRequest.cs +++ b/src/IoTHub.Portal.Shared/Models/v1.0/IdeaRequest.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Shared.Models.v1._0 +namespace IoTHub.Portal.Shared.Models.v10 { using System.ComponentModel.DataAnnotations; diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/IdeaResponse.cs b/src/IoTHub.Portal.Shared/Models/v1.0/IdeaResponse.cs index 8e9ee143e..356e0b360 100644 --- a/src/IoTHub.Portal.Shared/Models/v1.0/IdeaResponse.cs +++ b/src/IoTHub.Portal.Shared/Models/v1.0/IdeaResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Shared.Models.v1._0 +namespace IoTHub.Portal.Shared.Models.v10 { public class IdeaResponse { diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/LoRaWAN/LoRaGatewayIDList.cs b/src/IoTHub.Portal.Shared/Models/v1.0/LoRaWAN/LoRaGatewayIDList.cs index 845a86492..829939a61 100644 --- a/src/IoTHub.Portal.Shared/Models/v1.0/LoRaWAN/LoRaGatewayIDList.cs +++ b/src/IoTHub.Portal.Shared/Models/v1.0/LoRaWAN/LoRaGatewayIDList.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Shared.Models.v1._0 +namespace IoTHub.Portal.Shared.Models.v10 { using System.Collections.Generic; diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/PaginatedResult.cs b/src/IoTHub.Portal.Shared/Models/v1.0/PaginatedResult.cs index b47ac3fed..203426f09 100644 --- a/src/IoTHub.Portal.Shared/Models/v1.0/PaginatedResult.cs +++ b/src/IoTHub.Portal.Shared/Models/v1.0/PaginatedResult.cs @@ -3,7 +3,7 @@ #nullable enable -namespace IoTHub.Portal.Shared.Models.v1._0 +namespace IoTHub.Portal.Shared.Models.v10 { using System; using System.Collections.Generic; diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/PortalMetric.cs b/src/IoTHub.Portal.Shared/Models/v1.0/PortalMetric.cs index 952ddf087..d9eff2074 100644 --- a/src/IoTHub.Portal.Shared/Models/v1.0/PortalMetric.cs +++ b/src/IoTHub.Portal.Shared/Models/v1.0/PortalMetric.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Shared.Models.v1._0 +namespace IoTHub.Portal.Shared.Models.v10 { public class PortalMetric { diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/RoleDetailsModel.cs b/src/IoTHub.Portal.Shared/Models/v1.0/RoleDetailsModel.cs new file mode 100644 index 000000000..6e59afb31 --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/RoleDetailsModel.cs @@ -0,0 +1,15 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10 +{ + using System.Collections.Generic; + public class RoleDetailsModel + { + public string? Id { get; set; } + public string Name { get; set; } + public string Color { get; set; } + public string Description { get; set; } + public ICollection Actions { get; set; } = new List(); + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/RoleModel.cs b/src/IoTHub.Portal.Shared/Models/v1.0/RoleModel.cs new file mode 100644 index 000000000..f8a0fa687 --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/RoleModel.cs @@ -0,0 +1,13 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10 +{ + + public class RoleModel + { + public string Id { get; set; } + public string Name { get; set; } + public string Color { get; set; } + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/UserDetailsModel.cs b/src/IoTHub.Portal.Shared/Models/v1.0/UserDetailsModel.cs new file mode 100644 index 000000000..5b830dda3 --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/UserDetailsModel.cs @@ -0,0 +1,19 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10 +{ + using System.Collections.Generic; + public class UserDetailsModel + { + public string Id { get; set; } + public string Email { get; set; } + public string GivenName { get; set; } + public string Name { get; set; } + public string FamilyName { get; set; } + public string Avatar { get; set; } + public string PrincipalId { get; set; } + public ICollection Groups { get; set; } = new List(); + public ICollection AccessControls { get; set; } = new List(); + } +} diff --git a/src/IoTHub.Portal.Shared/Models/v1.0/UserModel.cs b/src/IoTHub.Portal.Shared/Models/v1.0/UserModel.cs new file mode 100644 index 000000000..2878151bb --- /dev/null +++ b/src/IoTHub.Portal.Shared/Models/v1.0/UserModel.cs @@ -0,0 +1,14 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Shared.Models.v10 +{ + + public class UserModel + { + public string Id { get; set; } + public string Email { get; set; } + public string GivenName { get; set; } + public string PrincipalId { get; set; } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Components/Dashboard/DashboardMetricsTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Components/Dashboard/DashboardMetricsTests.cs index b70b18b31..8aaf73084 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Components/Dashboard/DashboardMetricsTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Components/Dashboard/DashboardMetricsTests.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Tests.Unit.Components.Dashboard using IoTHub.Portal.Client.Exceptions; using IoTHub.Portal.Client.Models; using IoTHub.Portal.Client.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using Bunit; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/EditDeviceTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/EditDeviceTests.cs index 6b407a104..a4dc6f2a0 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/EditDeviceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/EditDeviceTests.cs @@ -23,7 +23,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Client.Components.Devices using IoTHub.Portal.Models.v10.LoRaWAN; using IoTHub.Portal.Shared.Constants; using IoTHub.Portal.Shared.Models; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using IoTHub.Portal.Client.Dialogs.Devices; using IoTHub.Portal.Tests.Unit.UnitTests.Bases; diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/LoRaWan/CreateLoraDeviceTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/LoRaWan/CreateLoraDeviceTests.cs index cca280162..0385db8a3 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/LoRaWan/CreateLoraDeviceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/LoRaWan/CreateLoraDeviceTests.cs @@ -13,7 +13,7 @@ namespace IoTHub.Portal.Tests.Unit.Client.Pages.Devices using IoTHub.Portal.Client.Services; using IoTHub.Portal.Client.Validators; using IoTHub.Portal.Models.v10.LoRaWAN; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using Bunit; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/LoRaWan/EditLoraDeviceTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/LoRaWan/EditLoraDeviceTests.cs index e528a22a1..5baa03cb1 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/LoRaWan/EditLoraDeviceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Components/Devices/LoRaWan/EditLoraDeviceTests.cs @@ -15,7 +15,7 @@ namespace IoTHub.Portal.Tests.Unit.Client.Components.Devices.LoRaWan using IoTHub.Portal.Client.Services; using IoTHub.Portal.Client.Validators; using IoTHub.Portal.Models.v10.LoRaWAN; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using Bunit; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Pages/Dashboard/DashboardTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Pages/Dashboard/DashboardTests.cs index 6e9169197..6d6ae05d6 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Pages/Dashboard/DashboardTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Pages/Dashboard/DashboardTests.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Tests.Unit.Client.Pages.Dashboard using System.Linq; using IoTHub.Portal.Client.Pages.Dashboard; using IoTHub.Portal.Client.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using UnitTests.Bases; using Bunit; using FluentAssertions; diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Pages/Ideas/SubmitIdeaDialogTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Pages/Ideas/SubmitIdeaDialogTests.cs index b9e7d6d57..8a9b519d1 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Pages/Ideas/SubmitIdeaDialogTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Pages/Ideas/SubmitIdeaDialogTests.cs @@ -11,7 +11,7 @@ namespace IoTHub.Portal.Tests.Unit.Client.Pages.Ideas using IoTHub.Portal.Client.Models; using IoTHub.Portal.Client.Dialogs.Ideas; using IoTHub.Portal.Client.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using UnitTests.Bases; using Bunit; using FluentAssertions; diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Pages/IndexTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Pages/IndexTests.cs index 7d8fbaf9b..4c83c6e2f 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Pages/IndexTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Pages/IndexTests.cs @@ -4,7 +4,7 @@ namespace IoTHub.Portal.Tests.Unit.Client.Pages { using IoTHub.Portal.Client.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using UnitTests.Bases; using Bunit; using FluentAssertions; diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Services/DashboardMetricsClientServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Services/DashboardMetricsClientServiceTests.cs index e264cebd8..b4799341c 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Services/DashboardMetricsClientServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Services/DashboardMetricsClientServiceTests.cs @@ -13,7 +13,7 @@ namespace IoTHub.Portal.Tests.Unit.Client.Services using Portal.Client.Exceptions; using Portal.Client.Models; using RichardSzalay.MockHttp; - using Shared.Models.v1._0; + using Shared.Models.v10; using static System.Net.Mime.MediaTypeNames; [TestFixture] diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Services/IdeaClientServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Services/IdeaClientServiceTests.cs index d45d36cb0..e226888e9 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Services/IdeaClientServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Services/IdeaClientServiceTests.cs @@ -13,7 +13,7 @@ namespace IoTHub.Portal.Tests.Unit.Client.Services using NUnit.Framework; using Portal.Client.Services; using RichardSzalay.MockHttp; - using Shared.Models.v1._0; + using Shared.Models.v10; [TestFixture] public class IdeaClientServiceTests : BlazorUnitTest diff --git a/src/IoTHub.Portal.Tests.Unit/Client/Services/LoRaWanDeviceClientServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Client/Services/LoRaWanDeviceClientServiceTests.cs index f6baa58f8..c86476c8a 100644 --- a/src/IoTHub.Portal.Tests.Unit/Client/Services/LoRaWanDeviceClientServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Client/Services/LoRaWanDeviceClientServiceTests.cs @@ -15,7 +15,7 @@ namespace IoTHub.Portal.Tests.Unit.Client.Services using Models.v10.LoRaWAN; using NUnit.Framework; using RichardSzalay.MockHttp; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using System.Linq; using IoTHub.Portal.Shared.Models.v10; diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/ConcentratorMetricExporterJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/ConcentratorMetricExporterJobTests.cs index d29286bbc..eefadcf63 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/ConcentratorMetricExporterJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/ConcentratorMetricExporterJobTests.cs @@ -6,7 +6,7 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Jobs using System; using IoTHub.Portal.Domain.Shared.Constants; using IoTHub.Portal.Infrastructure.Jobs; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/ConcentratorMetricLoaderJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/ConcentratorMetricLoaderJobTests.cs index efd81222d..68a2ef9b8 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/ConcentratorMetricLoaderJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/ConcentratorMetricLoaderJobTests.cs @@ -7,7 +7,7 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Jobs using IoTHub.Portal.Application.Services; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Infrastructure.Jobs; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/DeviceMetricExporterJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/DeviceMetricExporterJobTests.cs index 7b4d554f1..a09ffeba6 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/DeviceMetricExporterJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/DeviceMetricExporterJobTests.cs @@ -6,7 +6,7 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Jobs using System; using IoTHub.Portal.Domain.Shared.Constants; using IoTHub.Portal.Infrastructure.Jobs; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/DeviceMetricLoaderJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/DeviceMetricLoaderJobTests.cs index 39bba5eb3..ddc881a74 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/DeviceMetricLoaderJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/DeviceMetricLoaderJobTests.cs @@ -7,7 +7,7 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Jobs using IoTHub.Portal.Application.Services; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Infrastructure.Jobs; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/EdgeDeviceMetricExporterJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/EdgeDeviceMetricExporterJobTests.cs index 6d6c106bf..4575eb9e6 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/EdgeDeviceMetricExporterJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/EdgeDeviceMetricExporterJobTests.cs @@ -6,7 +6,7 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Jobs using System; using IoTHub.Portal.Domain.Shared.Constants; using IoTHub.Portal.Infrastructure.Jobs; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/EdgeDeviceMetricLoaderJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/EdgeDeviceMetricLoaderJobTests.cs index 33c868d7a..409c15f45 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/EdgeDeviceMetricLoaderJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/EdgeDeviceMetricLoaderJobTests.cs @@ -8,7 +8,7 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Jobs using IoTHub.Portal.Application.Services; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Infrastructure.Jobs; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/SyncGatewayIDJobTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/SyncGatewayIDJobTests.cs index eef060c2a..2f50ada80 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/SyncGatewayIDJobTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Jobs/SyncGatewayIDJobTests.cs @@ -7,7 +7,7 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Jobs using System.Collections.Generic; using IoTHub.Portal.Application.Services; using IoTHub.Portal.Infrastructure.Jobs; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/AccessControlRepositoryTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/AccessControlRepositoryTest.cs new file mode 100644 index 000000000..cec2b8f81 --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/AccessControlRepositoryTest.cs @@ -0,0 +1,62 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Infrastructure.Repositories; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using FluentAssertions; + using NUnit.Framework; + using AutoFixture; + + public class AccessControlRepositoryTest : BackendUnitTest + { + private AccessControlRepository accessControlRepository; + + public override void Setup() + { + base.Setup(); + + Fixture.Behaviors.OfType().ToList() + .ForEach(b => Fixture.Behaviors.Remove(b)); + Fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + this.accessControlRepository = new AccessControlRepository(DbContext); + } + + [Test] + public async Task GetAllShouldReturnExpectedAccessControls() + { + // Arrange + var expectedAccessControls = Fixture.CreateMany(2).ToList(); + + await DbContext.AddRangeAsync(expectedAccessControls); + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = this.accessControlRepository.GetAll().ToList(); + + // Assert + _ = result.Should().BeEquivalentTo(expectedAccessControls); + } + + [Test] + public async Task GetByIdAsync_ExistingAccessControl_ReturnsExpectedAccessControl() + { + // Arrange + var expectedAccessControl = Fixture.Create(); + + _ = DbContext.Add(expectedAccessControl); + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = await this.accessControlRepository.GetByIdAsync(expectedAccessControl.Id); + + // Assert + _ = result.Should().BeEquivalentTo(expectedAccessControl); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/ActionRepositoryTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/ActionRepositoryTest.cs new file mode 100644 index 000000000..35ad01f09 --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/ActionRepositoryTest.cs @@ -0,0 +1,60 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using AutoFixture; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Infrastructure.Repositories; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using FluentAssertions; + using NUnit.Framework; + + public class ActionRepositoryTest : BackendUnitTest + { + private ActionRepository actionRepository; + + public override void Setup() + { + base.Setup(); + + this.actionRepository = new ActionRepository(DbContext); + } + + [Test] + public async Task GetAllShouldReturnExpectedActions() + { + // Arrange + var expectedActions = Fixture.CreateMany(5).ToList(); + + await DbContext.AddRangeAsync(expectedActions); + + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = this.actionRepository.GetAll().ToList(); + + // Assert + _ = result.Should().BeEquivalentTo(expectedActions); + } + + [Test] + public async Task GetByIdAsync_ExistingAction_ReturnsExpectedAction() + { + // Arrange + var expectedAction = Fixture.Create(); + + _ = DbContext.Add(expectedAction); + + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = await this.actionRepository.GetByIdAsync(expectedAction.Id); + + // Assert + _ = result.Should().BeEquivalentTo(expectedAction); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GenericRepositoryTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GenericRepositoryTests.cs index b83e4503a..9d0d008cb 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GenericRepositoryTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GenericRepositoryTests.cs @@ -13,6 +13,7 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Repositories using FluentAssertions; using Microsoft.EntityFrameworkCore; using NUnit.Framework; + using IoTHub.Portal.Crosscutting; [TestFixture] public class GenericRepositoryTests : RepositoryTestBase diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GroupRepositoryTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GroupRepositoryTest.cs new file mode 100644 index 000000000..b1926b0ad --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GroupRepositoryTest.cs @@ -0,0 +1,62 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Infrastructure.Repositories; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using FluentAssertions; + using NUnit.Framework; + using AutoFixture; + + public class GroupRepositoryTest : BackendUnitTest + { + private GroupRepository groupRepository; + + public override void Setup() + { + base.Setup(); + + Fixture.Behaviors.OfType().ToList() + .ForEach(b => Fixture.Behaviors.Remove(b)); + Fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + this.groupRepository = new GroupRepository(DbContext); + } + + [Test] + public async Task GetAllShouldReturnExpectedGroups() + { + // Arrange + var expectedGroups = Fixture.CreateMany(2).ToList(); + + await DbContext.AddRangeAsync(expectedGroups); + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = this.groupRepository.GetAll().ToList(); + + // Assert + _ = result.Should().BeEquivalentTo(expectedGroups); + } + + [Test] + public async Task GetByIdAsync_ExistingGroup_ReturnsExpectedGroup() + { + // Arrange + var expectedGroup = Fixture.Create(); + + _ = DbContext.Add(expectedGroup); + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = await this.groupRepository.GetByIdAsync(expectedGroup.Id); + + // Assert + _ = result.Should().BeEquivalentTo(expectedGroup); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/PrincipalRepositoryTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/PrincipalRepositoryTest.cs new file mode 100644 index 000000000..e1f008954 --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/PrincipalRepositoryTest.cs @@ -0,0 +1,77 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Infrastructure.Repositories; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using FluentAssertions; + using NUnit.Framework; + using System.Collections.Generic; + using System; + + public class PrincipalRepositoryTest : BackendUnitTest + { + private PrincipalRepository principalRepository; + + public override void Setup() + { + base.Setup(); + + this.principalRepository = new PrincipalRepository(DbContext); + } + + [Test] + public async Task GetAllShouldReturnExpectedPrincipals() + { + // Arrange + var expectedPrincipals = new List + { + new Principal + { + Id = Guid.NewGuid().ToString(), + AccessControls = new List() + }, + new Principal + { + Id = Guid.NewGuid().ToString(), + AccessControls = new List() + } + }; + + await DbContext.AddRangeAsync(expectedPrincipals); + + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = this.principalRepository.GetAll().ToList(); + + // Assert + _ = result.Should().BeEquivalentTo(expectedPrincipals); + } + + [Test] + public async Task GetByIdAsync_ExistingPrincipal_ReturnsExpectedPrincipal() + { + // Arrange + var expectedPrincipal = new Principal + { + Id = Guid.NewGuid().ToString(), + AccessControls = new List() + }; + + _ = DbContext.Add(expectedPrincipal); + + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = await this.principalRepository.GetByIdAsync(expectedPrincipal.Id); + + // Assert + _ = result.Should().BeEquivalentTo(expectedPrincipal); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/RoleRepositoryTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/RoleRepositoryTest.cs new file mode 100644 index 000000000..e8ea75cfa --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/RoleRepositoryTest.cs @@ -0,0 +1,60 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using AutoFixture; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Infrastructure.Repositories; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using FluentAssertions; + using NUnit.Framework; + + public class RoleRepositoryTest : BackendUnitTest + { + private RoleRepository roleRepository; + + public override void Setup() + { + base.Setup(); + + this.roleRepository = new RoleRepository(DbContext); + } + + [Test] + public async Task GetAllShouldReturnExpectedRoles() + { + // Arrange + var expectedRoles = Fixture.CreateMany(5).ToList(); + + await DbContext.AddRangeAsync(expectedRoles); + + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = this.roleRepository.GetAll().ToList(); + + // Assert + _ = result.Should().BeEquivalentTo(expectedRoles); + } + + [Test] + public async Task GetByIdAsync_ExistingRole_ReturnsExpectedRole() + { + // Arrange + var expectedRole = Fixture.Create(); + + _ = DbContext.Add(expectedRole); + + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = await this.roleRepository.GetByIdAsync(expectedRole.Id); + + // Assert + _ = result.Should().BeEquivalentTo(expectedRole); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/UserRepositoryTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/UserRepositoryTest.cs new file mode 100644 index 000000000..90bc5fb51 --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Repositories/UserRepositoryTest.cs @@ -0,0 +1,62 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Repositories +{ + using System.Linq; + using System.Threading.Tasks; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Infrastructure.Repositories; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using FluentAssertions; + using NUnit.Framework; + using AutoFixture; + + public class UserRepositoryTest : BackendUnitTest + { + private UserRepository userRepository; + + public override void Setup() + { + base.Setup(); + + Fixture.Behaviors.OfType().ToList() + .ForEach(b => Fixture.Behaviors.Remove(b)); + Fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + this.userRepository = new UserRepository(DbContext); + } + + [Test] + public async Task GetAllShouldReturnExpectedUsers() + { + // Arrange + var expectedUsers = Fixture.CreateMany(2).ToList(); + + await DbContext.AddRangeAsync(expectedUsers); + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = this.userRepository.GetAll().ToList(); + + // Assert + _ = result.Should().BeEquivalentTo(expectedUsers); + } + + [Test] + public async Task GetByIdAsync_ExistingUser_ReturnsExpectedUser() + { + // Arrange + var expectedUser = Fixture.Create(); + + _ = DbContext.Add(expectedUser); + _ = await DbContext.SaveChangesAsync(); + + //Act + var result = await this.userRepository.GetByIdAsync(expectedUser.Id); + + // Assert + _ = result.Should().BeEquivalentTo(expectedUser); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AccessControlServiceTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AccessControlServiceTest.cs new file mode 100644 index 000000000..190547fe5 --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AccessControlServiceTest.cs @@ -0,0 +1,254 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Services +{ + using System; + using System.Linq; + using System.Linq.Expressions; + using System.Threading; + using System.Threading.Tasks; + using AutoFixture; + using AutoMapper; + using FluentAssertions; + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Domain; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Exceptions; + using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Shared.Models.v10; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using NUnit.Framework; + + public class AccessControlServiceTest : BackendUnitTest + { + private Mock mockUnitOfWork; + private Mock mockAccessControlRepository; + private Mock mockPrincipalRepository; + private Mock mockRoleRepository; + + private IAccessControlManagementService accessControlService; + + [SetUp] + public void Setup() + { + base.Setup(); + this.mockUnitOfWork = new Mock(); + this.mockAccessControlRepository = new Mock(); + this.mockRoleRepository = new Mock(); + this.mockPrincipalRepository = new Mock(); + + _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); + _ = ServiceCollection.AddSingleton(this.mockAccessControlRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockRoleRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockPrincipalRepository.Object); + _ = ServiceCollection.AddSingleton(); + + Services = ServiceCollection.BuildServiceProvider(); + + this.accessControlService = Services.GetRequiredService(); + Mapper = Services.GetRequiredService(); + + // Configure AutoFixture to avoid circular references + Fixture.Behaviors.OfType().ToList() + .ForEach(b => Fixture.Behaviors.Remove(b)); + Fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + } + + [Test] + public async Task GetAccessControlPageShouldReturnAList() + { + // Arrange + var role = Fixture.Create(); + var principal = Fixture.Create(); + + var accessControls = Fixture.Build() + .With(ac => ac.Role, role) + .With(ac => ac.Principal, principal) + .CreateMany(3) + .ToList(); + + var accessControlModelListItems = accessControls.Select(model => Mapper.Map(model)).ToList(); + + var paginatedResult = new PaginatedResult(accessControls, accessControlModelListItems.Count, 0, 10); + + _ = this.mockAccessControlRepository.Setup(repository => repository.GetPaginatedListAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>>(), + It.IsAny(), + It.IsAny>[]>() + )).ReturnsAsync(paginatedResult); + + // Act + var result = await this.accessControlService.GetAccessControlPage(); + + // Assert + _ = result.Data.Should().BeEquivalentTo(accessControlModelListItems); + MockRepository.VerifyAll(); + } + + [Test] + public async Task GetAccessControlShouldReturnExpectedValues() + { + var role = new RoleModel() + { + Id = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString(), + Color = Guid.NewGuid().ToString() + }; + // Arrange + var expectedAccessControl = new AccessControlModel() + { + Id = Guid.NewGuid().ToString(), + Scope = Guid.NewGuid().ToString(), + PrincipalId = Guid.NewGuid().ToString(), + Role = role + }; + + var accessControlEntity = Mapper.Map(expectedAccessControl); + + _ = this.mockAccessControlRepository.Setup(repository => repository.GetByIdAsync( + expectedAccessControl.Id, + It.IsAny>[]>() + )).ReturnsAsync(accessControlEntity); + + // Act + var result = await this.accessControlService.GetAccessControlAsync(expectedAccessControl.Id); + + // Assert + Assert.IsNotNull(result); + _ = result.Should().BeEquivalentTo(expectedAccessControl, options => options + .Excluding(r => r.Id) // Excluding Id because it is not mapped + .Excluding(r => r.Role) + .ComparingByMembers()); + } + + [Test] + public void GetAccessControlShouldThrowResourceNotFoundExceptionIfAccessControlDoesNotExist() + { + + // Arrange + _ = this.mockAccessControlRepository.Setup(x => x.GetByIdAsync(It.IsAny(), ac => ac.Role)) + .ReturnsAsync(value: null); + + // Act + var result = async () => await this.accessControlService.GetAccessControlAsync("test"); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + public async Task CreateRoleShouldCreate() + { + // Arrange + var accessControlModel = Fixture.Create(); + + var mockPrincipal = Fixture.Create(); + _ = this.mockPrincipalRepository.Setup(x => x.GetByIdAsync(accessControlModel.PrincipalId)) + .ReturnsAsync(mockPrincipal); + + var mockRole = Fixture.Create(); + _ = this.mockRoleRepository.Setup(x => x.GetByIdAsync(accessControlModel.Role.Id)) + .ReturnsAsync(mockRole); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + _ = await this.accessControlService.CreateAccessControl(accessControlModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void CreateAccessControlAlreadyExistsShouldThrowResourceAlreadyExistsException() + { + // Arrange + var accessControlModel = Fixture.Create(); + + var existingAccessControl = new AccessControl { Id = accessControlModel.Id }; + _ = this.mockAccessControlRepository.Setup(x => x.GetByIdAsync(accessControlModel.Id)) + .ReturnsAsync(existingAccessControl); + + // Act + Func act = async () => await this.accessControlService.CreateAccessControl(accessControlModel); + + // Assert + _ = act.Should().ThrowAsync(); + } + + [Test] + public async Task DeleteAccessControlShouldDeleteIt() + { + // Arrange + var accessControl = Fixture.Create(); + var accessControlId = accessControl.Id; + + _ = mockAccessControlRepository.Setup(x => x.GetByIdAsync(accessControlId)) + .ReturnsAsync(accessControl); + + // Act + _ = await accessControlService.DeleteAccessControl(accessControlId); + + // Assert + mockAccessControlRepository.Verify(x => x.Delete(accessControlId), Times.Once); + mockUnitOfWork.Verify(x => x.SaveAsync(), Times.Once); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void GetAccessControlDetailsAsync_InvalidId_ThrowsArgumentException(string invalidId) + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.accessControlService.GetAccessControlAsync(invalidId)); + } + + [Test] + public void CreateAccessControl_NulAccessControl_ThrowsArgumentNullException() + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.accessControlService.CreateAccessControl(null)); + } + + [Test] + public void UpdateAccessControWithANullAccessControl_ThrowsArgumentNullException() + { + // Arrange + var validId = Guid.NewGuid().ToString(); + + // Act & Assert + _ = Assert.ThrowsAsync(() => this.accessControlService.UpdateAccessControl(validId, null)); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void UpdateAccessControl_InvalidId_ThrowsArgumentException(string invalidId) + { + // Arrange + var accessControlModel = Fixture.Create(); + + // Act & Assert + _ = Assert.ThrowsAsync(() => this.accessControlService.UpdateAccessControl(invalidId, accessControlModel)); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void DeleteAccessControl_InvalidId_ThrowsArgumentException(string invalidId) + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.accessControlService.DeleteAccessControl(invalidId)); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AwsDeviceModelServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AwsDeviceModelServiceTests.cs index 4f42271a8..d9966cc88 100644 --- a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AwsDeviceModelServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/AwsDeviceModelServiceTests.cs @@ -15,7 +15,7 @@ namespace IoTHub.Portal.Tests.Unit.Infrastructure.Services using Moq; using AutoFixture; using IoTHub.Portal.Domain.Entities; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using System.Threading.Tasks; using System; diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/GroupServiceTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/GroupServiceTest.cs new file mode 100644 index 000000000..8426fe778 --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/GroupServiceTest.cs @@ -0,0 +1,261 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Services +{ + using System; + using System.Linq; + using System.Linq.Expressions; + using System.Threading; + using System.Threading.Tasks; + using AutoFixture; + using AutoMapper; + using FluentAssertions; + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Domain; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Exceptions; + using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Shared.Models.v10; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using NUnit.Framework; + + public class GroupServiceTest : BackendUnitTest + { + private Mock mockUnitOfWork; + private Mock mockGroupRepository; + private Mock mockPrincipalRepository; + private Mock mockUserRepository; + private Mock mockAccessControlRepository; + + private IGroupManagementService groupService; + + [SetUp] + public void Setup() + { + base.Setup(); + this.mockUnitOfWork = new Mock(); + this.mockGroupRepository = new Mock(); + this.mockPrincipalRepository = new Mock(); + this.mockUserRepository = new Mock(); + this.mockAccessControlRepository = new Mock(); + + _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); + _ = ServiceCollection.AddSingleton(this.mockGroupRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockPrincipalRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockUserRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockAccessControlRepository.Object); + _ = ServiceCollection.AddSingleton(); + + Services = ServiceCollection.BuildServiceProvider(); + + this.groupService = Services.GetRequiredService(); + Mapper = Services.GetRequiredService(); + } + + [Test] + public async Task GetGroupShouldReturnAList() + { + //Arrange + // Arrange + var expectedGroups = Fixture.CreateMany(3).ToList(); + var expectedGroupsList = expectedGroups.Select(group => Mapper.Map(group)).ToList(); + + var paginatedResult = new PaginatedResult(expectedGroupsList, expectedGroupsList.Count, 0, 10); + + _ = this.mockGroupRepository.Setup(repository => repository.GetPaginatedListAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>>(), + It.IsAny(), + It.IsAny>[]>() + )).ReturnsAsync(paginatedResult); + + //Act + var result = await this.groupService.GetGroupPage(); + + //Assert + _ = result.Data.Should().BeEquivalentTo(expectedGroups, options => options.Excluding(group => group.Id)); + MockRepository.VerifyAll(); + + } + + [Test] + public async Task GetGroupShouldReturnExpectedValues() + { + // Arrange + var expectedActions = Fixture.CreateMany(2).ToList(); + + var expectedGroup = new GroupDetailsModel() + { + Id = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString(), + Color = Guid.NewGuid().ToString(), + Description = Guid.NewGuid().ToString() + }; + + var expectedGroupEntity = Mapper.Map(expectedGroup); + + _ = this.mockGroupRepository.Setup(repository => repository.GetByIdAsync( + expectedGroup.Id, + It.IsAny>[]>() + )).ReturnsAsync(expectedGroupEntity); + + // Act + var result = await this.groupService.GetGroupDetailsAsync(expectedGroup.Id); + + // Assert + Assert.IsNotNull(result); + _ = result.Should().BeEquivalentTo(expectedGroup, options => options + .Excluding(r => r.Id) // Excluding Id because it is not mapped + .ComparingByMembers()); + } + + [Test] + public void GetGroupShouldThrowResourceNotFoundExceptionIfGroupDoesNotExist() + { + + // Arrange + _ = this.mockGroupRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync(value: null); + + // Act + var result = async () => await this.groupService.GetGroupDetailsAsync("test"); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void GetGroupDetailsAsyncInvalidIdThrowsResourceNotFoundException(string invalidId) + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.groupService.GetGroupDetailsAsync(invalidId)); + } + + [Test] + public async Task CreateGroupShouldCreate() + { + // Arrange + + var groupModel = Fixture.Create(); + + _ = this.mockGroupRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync((Group)null); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + _ = await this.groupService.CreateGroupAsync(groupModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void CreateGroupShouldThrowResourceAlreadyExistsExceptionIfGroupAlreadyExists() + { + // Arrange + var groupModel = Fixture.Create(); + + var existingGroup = new Group { Name = groupModel.Name }; + _ = this.mockGroupRepository.Setup(x => x.GetByNameAsync(groupModel.Name)) + .ReturnsAsync(existingGroup); + + // Act + Func act = async () => await this.groupService.CreateGroupAsync(groupModel); + + // Assert + _ = act.Should().ThrowAsync(); + } + + [Test] + public void CreateGroupWithNullGroupThrowsArgumentNullException() + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.groupService.CreateGroupAsync(null)); + } + + [Test] + public async Task UpdateGroupShouldUpdateGroup() + { + // Arrange + + var groupModel = Fixture.Create(); + var groupEntity = Mapper.Map(groupModel); + + _ = this.mockGroupRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync(groupEntity); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + _ = await this.groupService.UpdateGroup(groupModel.Id, groupModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void UpdateGroupWithNullGroupThrowsArgumentNullException() + { + // Arrange + var validId = Guid.NewGuid().ToString(); + + // Act & Assert + _ = Assert.ThrowsAsync(() => this.groupService.UpdateGroup(validId, null)); + } + + [Test] + [TestCase(null)] + [TestCase("")] + + [TestCase(" ")] + public void UpdateGroupWithInvalidIdThrowsResourceNotFoundException(string invalidId) + { + // Arrange + var groupModel = Fixture.Create(); + + // Act & Assert + _ = Assert.ThrowsAsync(() => this.groupService.UpdateGroup(invalidId, groupModel)); + } + + [Test] + public async Task DeleteGroupShouldDeleteGroup() + { + // Arrange + var groupModel = Fixture.Create(); + var groupEntity = Mapper.Map(groupModel); + + _ = this.mockGroupRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync(groupEntity); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + var result = await this.groupService.DeleteGroup(groupModel.Id); + + // Assert + Assert.IsTrue(result); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void DeleteGroupWithInvalidIdThrowsResourceNotFoundException(string invalidId) + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.groupService.DeleteGroup(invalidId)); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/RoleServiceTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/RoleServiceTest.cs new file mode 100644 index 000000000..49e1c550c --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/RoleServiceTest.cs @@ -0,0 +1,263 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Services +{ + using System; + using System.Linq; + using System.Linq.Expressions; + using System.Threading; + using System.Threading.Tasks; + using AutoFixture; + using AutoMapper; + using FluentAssertions; + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Domain; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Exceptions; + using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Shared.Models.v10; + using IoTHub.Portal.Shared.Models.v10.Filters; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using NUnit.Framework; + + public class RoleServiceTest : BackendUnitTest + { + private Mock mockUnitOfWork; + private Mock mockRoleRepository; + private Mock mockActionRepository; + + private IRoleManagementService roleService; + + [SetUp] + public void Setup() + { + base.Setup(); + this.mockUnitOfWork = new Mock(); + this.mockRoleRepository = new Mock(); + this.mockActionRepository = new Mock(); + this.mockActionRepository = new Mock(); + + _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); + _ = ServiceCollection.AddSingleton(this.mockRoleRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockActionRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockActionRepository.Object); + _ = ServiceCollection.AddSingleton(); + + Services = ServiceCollection.BuildServiceProvider(); + + this.roleService = Services.GetRequiredService(); + Mapper = Services.GetRequiredService(); + } + + [Test] + public async Task GetRoleShouldReturnAList() + { + //Arrange + // Arrange + var expectedRoles = Fixture.CreateMany(3).ToList(); + var expectedRolesList = expectedRoles.Select(role => Mapper.Map(role)).ToList(); + + var paginatedResult = new PaginatedResult(expectedRolesList, expectedRolesList.Count, 0, 10); + + var roleFilter = new RoleFilter + { + Keyword = new Guid().ToString(), + }; + + _ = this.mockRoleRepository.Setup(repository => repository.GetPaginatedListAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>>(), + It.IsAny(), + It.IsAny>[]>() + )).ReturnsAsync(paginatedResult); + + //Act + var result = await this.roleService.GetRolePage(roleFilter.Keyword, roleFilter.PageSize, roleFilter.PageNumber, roleFilter.OrderBy); + + //Assert + _ = result.Data.Should().BeEquivalentTo(expectedRoles); + MockRepository.VerifyAll(); + + } + + [Test] + public async Task GetRoleShouldReturnExpectedValues() + { + // Arrange + var expectedActions = Fixture.CreateMany(2).ToList(); + + var expectedRole = new RoleDetailsModel() + { + Id = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString(), + Color = Guid.NewGuid().ToString(), + Description = Guid.NewGuid().ToString(), + Actions = expectedActions + }; + + var expectedRoleEntity = Mapper.Map(expectedRole); + + _ = this.mockRoleRepository.Setup(repository => repository.GetByIdAsync( + expectedRole.Id, + It.IsAny>[]>() + )).ReturnsAsync(expectedRoleEntity); + + // Act + var result = await this.roleService.GetRoleDetailsAsync(expectedRole.Id); + + // Assert + Assert.IsNotNull(result); + _ = result.Should().BeEquivalentTo(expectedRole, options => options + .Excluding(r => r.Id) // Excluding Id because it is not mapped + .ComparingByMembers()); + } + + [Test] + public void GetRoleShouldThrowResourceNotFoundExceptionIfRoleDoesNotExist() + { + + // Arrange + _ = this.mockRoleRepository.Setup(x => x.GetByIdAsync(It.IsAny(), r => r.Actions)) + .ReturnsAsync(value: null); + + // Act + var result = async () => await this.roleService.GetRoleDetailsAsync("test"); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void GetRoleDetailsAsync_InvalidId_ThrowsArgumentException(string invalidId) + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.roleService.GetRoleDetailsAsync(invalidId)); + } + + [Test] + public async Task CreateRoleShouldCreate() + { + // Arrange + + var roleModel = Fixture.Create(); + + _ = this.mockRoleRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync((Role)null); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + _ = await this.roleService.CreateRole(roleModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void CreateRoleShouldThrowResourceAlreadyExistsExceptionIfRoleAlreadyExists() + { + // Arrange + var roleModel = Fixture.Create(); + + var existingRole = new Role { Name = roleModel.Name }; + _ = this.mockRoleRepository.Setup(x => x.GetByNameAsync(roleModel.Name)) + .ReturnsAsync(existingRole); + + // Act + Func act = async () => await this.roleService.CreateRole(roleModel); + + // Assert + _ = act.Should().ThrowAsync(); + } + + [Test] + public void CreateRole_NullRole_ThrowsArgumentNullException() + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.roleService.CreateRole(null)); + } + + [Test] + public async Task UpdateRoleShouldUpdateRole() + { + // Arrange + + var roleModel = Fixture.Create(); + var roleEntity = Mapper.Map(roleModel); + + _ = this.mockRoleRepository.Setup(x => x.GetByIdAsync(It.IsAny(), r => r.Actions)) + .ReturnsAsync(roleEntity); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + _ = await this.roleService.UpdateRole(roleModel.Id, roleModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void UpdateRole_NullRole_ThrowsArgumentNullException() + { + // Arrange + var validId = Guid.NewGuid().ToString(); + + // Act & Assert + _ = Assert.ThrowsAsync(() => this.roleService.UpdateRole(validId, null)); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void UpdateRole_InvalidId_ThrowsArgumentException(string invalidId) + { + // Arrange + var roleModel = Fixture.Create(); + + // Act & Assert + _ = Assert.ThrowsAsync(() => this.roleService.UpdateRole(invalidId, roleModel)); + } + + [Test] + public async Task DeleteRoleShouldDeleteRole() + { + // Arrange + var roleModel = Fixture.Create(); + var roleEntity = Mapper.Map(roleModel); + + _ = this.mockRoleRepository.Setup(x => x.GetByIdAsync(It.IsAny(), r => r.Actions)) + .ReturnsAsync(roleEntity); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + var result = await this.roleService.DeleteRole(roleModel.Id); + + // Assert + Assert.IsTrue(result); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void DeleteRole_InvalidId_ThrowsArgumentException(string invalidId) + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.roleService.DeleteRole(invalidId)); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/UserServiceTest.cs b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/UserServiceTest.cs new file mode 100644 index 000000000..26e471c0d --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Infrastructure/Services/UserServiceTest.cs @@ -0,0 +1,257 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Infrastructure.Services +{ + using System; + using System.Linq; + using System.Linq.Expressions; + using System.Threading; + using System.Threading.Tasks; + using AutoFixture; + using AutoMapper; + using FluentAssertions; + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Domain; + using IoTHub.Portal.Domain.Entities; + using IoTHub.Portal.Domain.Exceptions; + using IoTHub.Portal.Domain.Repositories; + using IoTHub.Portal.Shared.Models.v10; + using IoTHub.Portal.Tests.Unit.UnitTests.Bases; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using NUnit.Framework; + + public class UserServiceTest : BackendUnitTest + { + private Mock mockUnitOfWork; + private Mock mockUserRepository; + private Mock mockPrincipalRepository; + + private IUserManagementService userService; + + [SetUp] + public void Setup() + { + base.Setup(); + this.mockUnitOfWork = new Mock(); + this.mockUserRepository = new Mock(); + this.mockPrincipalRepository = new Mock(); + + _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); + _ = ServiceCollection.AddSingleton(this.mockUserRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockPrincipalRepository.Object); + _ = ServiceCollection.AddSingleton(); + + Services = ServiceCollection.BuildServiceProvider(); + + this.userService = Services.GetRequiredService(); + Mapper = Services.GetRequiredService(); + } + + [Test] + public async Task GetUserShouldReturnAList() + { + //Arrange + // Arrange + var expectedUsers = Fixture.CreateMany(3).ToList(); + var expectedUsersList = expectedUsers.Select(user => Mapper.Map(user)).ToList(); + + var paginatedResult = new PaginatedResult(expectedUsersList, expectedUsersList.Count, 0, 10); + + _ = this.mockUserRepository.Setup(repository => repository.GetPaginatedListAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>>(), + It.IsAny(), + It.IsAny>[]>() + )).ReturnsAsync(paginatedResult); + + //Act + var result = await this.userService.GetUserPage(); + + //Assert + _ = result.Data.Should().BeEquivalentTo(expectedUsers, options => options.Excluding(user => user.Id)); + MockRepository.VerifyAll(); + + } + + [Test] + public async Task GetUserShouldReturnExpectedValues() + { + // Arrange + var expectedActions = Fixture.CreateMany(2).ToList(); + + var expectedUser = new UserDetailsModel() + { + Id = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString(), + Email = Guid.NewGuid().ToString(), + GivenName = Guid.NewGuid().ToString(), + FamilyName = Guid.NewGuid().ToString(), + Avatar = Guid.NewGuid().ToString(), + }; + + var expectedUserEntity = Mapper.Map(expectedUser); + + _ = this.mockUserRepository.Setup(repository => repository.GetByIdAsync( + expectedUser.Id, + It.IsAny>[]>() + )).ReturnsAsync(expectedUserEntity); + + // Act + var result = await this.userService.GetUserDetailsAsync(expectedUser.Id); + + // Assert + Assert.IsNotNull(result); + _ = result.Should().BeEquivalentTo(expectedUser, options => options + .Excluding(r => r.Id) // Excluding Id because it is not mapped + .ComparingByMembers()); + } + + [Test] + public void GetUserShouldThrowResourceNotFoundExceptionIfUserDoesNotExist() + { + + // Arrange + _ = this.mockUserRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync(value: null); + + // Act + var result = async () => await this.userService.GetUserDetailsAsync("test"); + + // Assert + _ = result.Should().ThrowAsync(); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void GetUserDetailsAsyncInvalidIdThrowsResourceNotFoundException(string invalidId) + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.userService.GetUserDetailsAsync(invalidId)); + } + + [Test] + public async Task CreateUserShouldCreate() + { + // Arrange + + var userModel = Fixture.Create(); + + _ = this.mockUserRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync((User)null); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + _ = await this.userService.CreateUserAsync(userModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void CreateUserShouldThrowResourceAlreadyExistsExceptionIfUserAlreadyExists() + { + // Arrange + var userModel = Fixture.Create(); + + var existingUser = new User { Name = userModel.Name }; + _ = this.mockUserRepository.Setup(x => x.GetByNameAsync(userModel.Name)) + .ReturnsAsync(existingUser); + + // Act + Func act = async () => await this.userService.CreateUserAsync(userModel); + + // Assert + _ = act.Should().ThrowAsync(); + } + + [Test] + public void CreateUserWithNullUserThrowsArgumentNullException() + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.userService.CreateUserAsync(null)); + } + + [Test] + public async Task UpdateUserShouldUpdateUser() + { + // Arrange + + var userModel = Fixture.Create(); + var userEntity = Mapper.Map(userModel); + + _ = this.mockUserRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync(userEntity); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + _ = await this.userService.UpdateUser(userModel.Id, userModel); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public void UpdateUserWithNullUserThrowsArgumentNullException() + { + // Arrange + var validId = Guid.NewGuid().ToString(); + + // Act & Assert + _ = Assert.ThrowsAsync(() => this.userService.UpdateUser(validId, null)); + } + + [Test] + [TestCase(null)] + [TestCase("")] + + [TestCase(" ")] + public void UpdateUserWithInvalidIdThrowsResourceNotFoundException(string invalidId) + { + // Arrange + var userModel = Fixture.Create(); + + // Act & Assert + _ = Assert.ThrowsAsync(() => this.userService.UpdateUser(invalidId, userModel)); + } + + [Test] + public async Task DeleteUserShouldDeleteUser() + { + // Arrange + var userModel = Fixture.Create(); + var userEntity = Mapper.Map(userModel); + + _ = this.mockUserRepository.Setup(x => x.GetByIdAsync(It.IsAny())) + .ReturnsAsync(userEntity); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + var result = await this.userService.DeleteUser(userModel.Id); + + // Assert + Assert.IsTrue(result); + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void DeleteUserWithInvalidIdThrowsResourceNotFoundException(string invalidId) + { + // Act & Assert + _ = Assert.ThrowsAsync(() => this.userService.DeleteUser(invalidId)); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/AccessControlsControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/AccessControlsControllerTests.cs new file mode 100644 index 000000000..6b4607ea7 --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/AccessControlsControllerTests.cs @@ -0,0 +1,413 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 +{ + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Server.Controllers.V10; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.Extensions.Logging; + using Moq; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Microsoft.AspNetCore.DataProtection; + using Microsoft.AspNetCore.Mvc; + using IoTHub.Portal.Models.v10; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.AspNetCore.Mvc.Routing; + using System.Linq; + using System; + using IoTHub.Portal.Server.Controllers.v10; + + [TestFixture] + public class AccessControlssControllerTests + { + private MockRepository mockRepository; + + private Mock> mockLogger; + private Mock mockAccessControlService; + private Mock mockUrlHelper; + private IDataProtectionProvider mockDataProtectionProvider; + + [SetUp] + public void SetUp() + { + this.mockRepository = new MockRepository(MockBehavior.Strict); + + this.mockLogger = this.mockRepository.Create>(); + this.mockAccessControlService = this.mockRepository.Create(); + this.mockUrlHelper = this.mockRepository.Create(); + this.mockDataProtectionProvider = new EphemeralDataProtectionProvider(); + } + + private AccessControlController CreateAccessControlsController() + { + return new AccessControlController( + this.mockLogger.Object, + this.mockAccessControlService.Object) + { + Url = this.mockUrlHelper.Object + }; + } + + [Test] + public async Task GetAllAccessControlsReturnOkResult() + { + // Arrange + var accessControlController = CreateAccessControlsController(); + + var paginedAccessControls = new PaginatedResult() + { + Data = Enumerable.Range(0, 10).Select(x => new AccessControlModel() + { + Id = FormattableString.Invariant($"{x}"), + }).ToList(), + TotalCount = 100, + PageSize = 10, + CurrentPage = 0 + }; + + _ = this.mockAccessControlService + .Setup(x => x.GetAccessControlPage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(paginedAccessControls); + + var locationUrl = "http://location/accessControls"; + + _ = this.mockUrlHelper + .Setup(x => x.RouteUrl(It.IsAny())) + .Returns(locationUrl); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await accessControlController.Get(); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(paginedAccessControls.Data.Count, result.Items.Count()); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task Get_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var accessControlController = CreateAccessControlsController(); + + _ = this.mockAccessControlService + .Setup(x => x.GetAccessControlPage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await accessControlController.Get()); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task GetByIdShouldReturnTheCorrespondantAccessControl() + { + // Arrange + var accessControlsController = CreateAccessControlsController(); + + var accessControlId = Guid.NewGuid().ToString(); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + _ = this.mockAccessControlService + .Setup(x => x.GetAccessControlPage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(new PaginatedResult() + { + Data = new List() { new AccessControlModel() { Id = accessControlId } }, + TotalCount = 1, + PageSize = 10, + CurrentPage = 0 + }); + + // Act + var result = await accessControlsController.Get(accessControlId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom>(result); + + var accessControl = result.Items.First(); + + Assert.IsNotNull(accessControl); + Assert.AreEqual(accessControlId, accessControl.Id); + + this.mockRepository.VerifyAll(); + } + + + [Test] + public async Task GetAccessControlDetailsShouldReturnNotFoundForNonExistingGroup() + { + // Arrange + var accessControlController = CreateAccessControlsController(); + var accessControlId = Guid.NewGuid().ToString(); + + _ = this.mockAccessControlService + .Setup(x => x.GetAccessControlAsync(It.IsAny())) + .ReturnsAsync((AccessControlModel)null); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Warning, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await accessControlController.GetACById(accessControlId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task CreateAccessControlShouldReturnOk() + { + // Arrange + var accessControlsController = CreateAccessControlsController(); + + var accessControl = new AccessControlModel() + { + Id = Guid.NewGuid().ToString() + }; + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockAccessControlService + .Setup(x => x.CreateAccessControl(It.Is(c => c.Id.Equals(accessControl.Id, StringComparison.Ordinal)))) + .ReturnsAsync(accessControl); + + // Act + var result = await accessControlsController.CreateAccessControlAsync(accessControl); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + var okObjectResult = result as OkObjectResult; + + Assert.IsNotNull(okObjectResult); + Assert.AreEqual(200, okObjectResult.StatusCode); + + Assert.IsNotNull(okObjectResult.Value); + Assert.IsAssignableFrom(okObjectResult.Value); + + var accessControlObj = okObjectResult.Value as AccessControlModel; + Assert.IsNotNull(accessControlObj); + Assert.AreEqual(accessControl.Id, accessControlObj.Id); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task CreateAccessControlShouldReturnBadRequestWhenCreationFails() + { + // Arrange + var accessControlController = CreateAccessControlsController(); + var accessControl = new AccessControlModel() + { + Id = Guid.NewGuid().ToString() + }; + + _ = this.mockAccessControlService + .Setup(x => x.CreateAccessControl(It.IsAny())) + .Throws(new Exception()); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await accessControlController.CreateAccessControlAsync(accessControl); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task UpdateAccessControlShouldReturnOkResult() + { + // Arrange + var accessControlsController = CreateAccessControlsController(); + + + var accessControlId = Guid.NewGuid().ToString(); + + var accessControl = new AccessControlModel() + { + Id = accessControlId + }; + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockAccessControlService + .Setup(x => x.UpdateAccessControl(accessControlId, It.Is(c => c.Id.Equals(accessControl.Id, StringComparison.Ordinal)))) + .ReturnsAsync(accessControl); + + // Act + var result = await accessControlsController.EditAccessControlAsync(accessControlId, accessControl); + + // Assert + Assert.IsNotNull(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task EditAccessControlAsync_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var acController = CreateAccessControlsController(); + var acId = Guid.NewGuid().ToString(); + var ac = new AccessControlModel() { Id = acId }; + + _ = this.mockAccessControlService + .Setup(x => x.UpdateAccessControl(acId, It.IsAny())) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await acController.EditAccessControlAsync(acId, ac)); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteAccessControlShouldReturnExpectedBehavior() + { + // Arrange + var accessControlsController = CreateAccessControlsController(); + var deviceId = Guid.NewGuid().ToString(); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockAccessControlService.Setup(c => c.DeleteAccessControl(It.Is(x => x == deviceId))) + .ReturnsAsync(true); + + // Act + var result = await accessControlsController.DeleteAccessControl(deviceId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteAccessControl_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var acController = CreateAccessControlsController(); + var acId = Guid.NewGuid().ToString(); + + _ = this.mockAccessControlService.Setup(c => c.DeleteAccessControl(It.Is(x => x == acId))) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await acController.DeleteAccessControl(acId)); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/AdminControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/AdminControllerTests.cs index 0b798ba72..9c72b9748 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/AdminControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/AdminControllerTests.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using System; using System.IO; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DashboardControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DashboardControllerTests.cs index 93198d5d6..b8617f6d5 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DashboardControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DashboardControllerTests.cs @@ -1,10 +1,10 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { - using IoTHub.Portal.Server.Controllers.v1._0; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Server.Controllers.v10; + using IoTHub.Portal.Shared.Models.v10; using FluentAssertions; using NUnit.Framework; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceConfigurationsControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceConfigurationsControllerTests.cs index a7cfe492e..6cd2af294 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceConfigurationsControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceConfigurationsControllerTests.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using System; using System.Collections.Generic; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelControllerBaseTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelControllerBaseTests.cs index 1ae7cdd38..c1702aa62 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelControllerBaseTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelControllerBaseTests.cs @@ -1,11 +1,11 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using AutoFixture; using IoTHub.Portal.Models.v10; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using System.Threading.Tasks; using IoTHub.Portal.Tests.Unit.UnitTests.Bases; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelPropertiesControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelPropertiesControllerTests.cs index 5acbc5097..18d9e203e 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelPropertiesControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelPropertiesControllerTests.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using System; using System.Collections.Generic; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelsControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelsControllerTests.cs index 9fc725ee0..68a28296b 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelsControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelsControllerTests.cs @@ -1,14 +1,14 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using System.Linq; using System.Threading.Tasks; using AutoFixture; using IoTHub.Portal.Application.Services; using IoTHub.Portal.Server.Controllers.V10; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using FluentAssertions; using Microsoft.AspNetCore.Http; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceTagSettingsControllerTest.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceTagSettingsControllerTest.cs index 0732c39aa..b3aa9935f 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceTagSettingsControllerTest.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceTagSettingsControllerTest.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using System; using System.Collections.Generic; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs index 454c8813b..66641dc00 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using System; using System.Collections.Generic; @@ -23,7 +23,7 @@ namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 using Models.v10; using Moq; using NUnit.Framework; - using Shared.Models.v1._0; + using Shared.Models.v10; [TestFixture] public class DevicesControllerTests diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeDevicesControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeDevicesControllerTests.cs index 1dd041f71..7eda7ad96 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeDevicesControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeDevicesControllerTests.cs @@ -15,7 +15,7 @@ namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 using IoTHub.Portal.Application.Services; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Server.Controllers.V10; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Tests.Unit.UnitTests.Bases; using FluentAssertions; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeModelsControllerTest.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeModelsControllerTest.cs index 3ec955b83..4071532c4 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeModelsControllerTest.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeModelsControllerTest.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using System; using System.Collections.Generic; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/GroupsControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/GroupsControllerTests.cs new file mode 100644 index 000000000..1b6d02cbd --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/GroupsControllerTests.cs @@ -0,0 +1,409 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 +{ + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Server.Controllers.V10; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.Extensions.Logging; + using Moq; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Microsoft.AspNetCore.DataProtection; + using Microsoft.AspNetCore.Mvc; + using IoTHub.Portal.Models.v10; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.AspNetCore.Mvc.Routing; + using System.Linq; + using System; + using Microsoft.AspNetCore.Http; + + [TestFixture] + public class GroupssControllerTests + { + private MockRepository mockRepository; + + private Mock> mockLogger; + private Mock mockGroupService; + private Mock mockAccessControlService; + private Mock mockUrlHelper; + private IDataProtectionProvider mockDataProtectionProvider; + + [SetUp] + public void SetUp() + { + this.mockRepository = new MockRepository(MockBehavior.Strict); + + this.mockLogger = this.mockRepository.Create>(); + this.mockGroupService = this.mockRepository.Create(); + this.mockAccessControlService = this.mockRepository.Create(); + this.mockUrlHelper = this.mockRepository.Create(); + this.mockDataProtectionProvider = new EphemeralDataProtectionProvider(); + } + + private GroupsController CreateGroupsController() + { + return new GroupsController( + this.mockGroupService.Object, + this.mockLogger.Object, + this.mockAccessControlService.Object) + { + Url = this.mockUrlHelper.Object + }; + } + + [Test] + public async Task GetAllGroupsReturnOkResult() + { + // Arrange + var groupController = CreateGroupsController(); + + var paginedGroups = new PaginatedResult() + { + Data = Enumerable.Range(0, 10).Select(x => new GroupModel() + { + Id = FormattableString.Invariant($"{x}"), + }).ToList(), + TotalCount = 100, + PageSize = 10, + CurrentPage = 0 + }; + + _ = this.mockGroupService + .Setup(x => x.GetGroupPage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(paginedGroups); + + var locationUrl = "http://location/groups"; + + _ = this.mockUrlHelper + .Setup(x => x.RouteUrl(It.IsAny())) + .Returns(locationUrl); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + + + // Act + var result = await groupController.Get(); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(paginedGroups.Data.Count, result.Items.Count()); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task Get_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var groupsController = CreateGroupsController(); + + _ = this.mockGroupService + .Setup(x => x.GetGroupPage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await groupsController.Get()); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task GetByIdShouldReturnTheCorrespondantGroup() + { + // Arrange + var groupsController = CreateGroupsController(); + + var groupId = Guid.NewGuid().ToString(); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + _ = this.mockGroupService + .Setup(x => x.GetGroupPage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(new PaginatedResult() + { + Data = new List() { new GroupModel() { Id = groupId } }, + TotalCount = 1, + PageSize = 10, + CurrentPage = 0 + }); + + // Act + var result = await groupsController.Get(groupId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom>(result); + + var group = result.Items.First(); + + Assert.IsNotNull(group); + Assert.AreEqual(groupId, group.Id); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task GetGroupDetailsShouldReturnNotFoundForNonExistingGroup() + { + // Arrange + var groupsController = CreateGroupsController(); + var groupId = Guid.NewGuid().ToString(); + + _ = this.mockGroupService + .Setup(x => x.GetGroupDetailsAsync(It.IsAny())) + .ReturnsAsync((GroupDetailsModel)null); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Warning, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await groupsController.GetGroupDetails(groupId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task CreateGroupShouldReturnOk() + { + // Arrange + var groupsController = CreateGroupsController(); + + var group = new GroupDetailsModel() + { + Id = Guid.NewGuid().ToString() + }; + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockGroupService + .Setup(x => x.CreateGroupAsync(It.Is(c => c.Id.Equals(group.Id, StringComparison.Ordinal)))) + .ReturnsAsync(group); + + // Act + var result = await groupsController.CreateGroup(group); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + var createdAtActionResult = result as CreatedAtActionResult; + + Assert.IsNotNull(createdAtActionResult); + Assert.AreEqual(201, createdAtActionResult.StatusCode); + + Assert.IsNotNull(createdAtActionResult.Value); + Assert.IsAssignableFrom(createdAtActionResult.Value); + + var groupObj = createdAtActionResult.Value as GroupDetailsModel; + Assert.IsNotNull(groupObj); + Assert.AreEqual(group.Id, groupObj.Id); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task CreateGroupShouldReturnBadRequestWhenCreationFails() + { + // Arrange + var groupsController = CreateGroupsController(); + var group = new GroupDetailsModel() + { + Id = Guid.NewGuid().ToString() + }; + + _ = this.mockGroupService + .Setup(x => x.CreateGroupAsync(It.IsAny())) + .Throws(new Exception()); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await groupsController.CreateGroup(group); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task UpdateGroupShouldReturnOkResult() + { + // Arrange + var groupsController = CreateGroupsController(); + + + var groupId = Guid.NewGuid().ToString(); + + var group = new GroupDetailsModel() + { + Id = groupId + }; + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockGroupService + .Setup(x => x.UpdateGroup(groupId, It.Is(c => c.Id.Equals(group.Id, StringComparison.Ordinal)))) + .ReturnsAsync(group); + + // Act + var result = await groupsController.EditGroupAsync(groupId, group); + + // Assert + Assert.IsNotNull(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task EditGroupAsync_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var groupsController = CreateGroupsController(); + var groupId = Guid.NewGuid().ToString(); + var group = new GroupDetailsModel() { Id = groupId }; + + _ = this.mockGroupService + .Setup(x => x.UpdateGroup(groupId, It.IsAny())) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await groupsController.EditGroupAsync(groupId, group)); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteGroupShouldReturnExpectedBehavior() + { + // Arrange + var groupsController = CreateGroupsController(); + var deviceId = Guid.NewGuid().ToString(); + + _ = this.mockGroupService.Setup(c => c.DeleteGroup(It.Is(x => x == deviceId))) + .ReturnsAsync(true); + + _ = this.mockLogger.Setup(x => x.Log( + It.Is(l => l == LogLevel.Information), + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await groupsController.DeleteGroup(deviceId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteGroup_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var groupsController = CreateGroupsController(); + var groupId = Guid.NewGuid().ToString(); + + _ = this.mockGroupService.Setup(c => c.DeleteGroup(It.Is(x => x == groupId))) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await groupsController.DeleteGroup(groupId)); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/IdeasControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/IdeasControllerTests.cs index 0b05fbe21..176b90bc6 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/IdeasControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/IdeasControllerTests.cs @@ -1,13 +1,13 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using System.Threading.Tasks; using AutoFixture; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Server.Controllers.v1._0; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Server.Controllers.v10; + using IoTHub.Portal.Shared.Models.v10; using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANCommandsControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANCommandsControllerTests.cs index 67c92d25c..86302572f 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANCommandsControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANCommandsControllerTests.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10.LoRaWAN { using System; using System.Threading.Tasks; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsControllerTest.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsControllerTest.cs index d1de7c5b6..b91254233 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsControllerTest.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsControllerTest.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10.LoRaWAN { using System; using System.Linq; @@ -10,7 +10,7 @@ namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN using IoTHub.Portal.Application.Services; using IoTHub.Portal.Models.v10.LoRaWAN; using IoTHub.Portal.Server.Controllers.V10.LoRaWAN; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using IoTHub.Portal.Tests.Unit.UnitTests.Bases; using FluentAssertions; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsControllerTest.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsControllerTest.cs index a9059055f..d996421a8 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsControllerTest.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsControllerTest.cs @@ -1,13 +1,13 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10.LoRaWAN { using System.Linq; using System.Threading.Tasks; using AutoFixture; using IoTHub.Portal.Application.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using FluentAssertions; using Microsoft.AspNetCore.Http; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesControllerTests.cs index 5109565aa..3e11e4150 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesControllerTests.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10.LoRaWAN { using System; using System.Collections.Generic; @@ -23,7 +23,7 @@ namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN using Models.v10; using Moq; using NUnit.Framework; - using Shared.Models.v1._0; + using Shared.Models.v10; [TestFixture] public class LoRaWANDevicesControllerTests diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANFrequencyPlansControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANFrequencyPlansControllerTests.cs index 4eb14c4cf..f0d7a6e97 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANFrequencyPlansControllerTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANFrequencyPlansControllerTests.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10.LoRaWAN { using System.Collections.Generic; using IoTHub.Portal.Server.Controllers.V10.LoRaWAN; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/RolesControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/RolesControllerTests.cs new file mode 100644 index 000000000..44353f004 --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/RolesControllerTests.cs @@ -0,0 +1,409 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 +{ + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Server.Controllers.V10; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.Extensions.Logging; + using Moq; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Microsoft.AspNetCore.DataProtection; + using Microsoft.AspNetCore.Mvc; + using IoTHub.Portal.Models.v10; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.AspNetCore.Mvc.Routing; + using System.Linq; + using System; + + [TestFixture] + public class RolesControllerTests + { + private MockRepository mockRepository; + + private Mock> mockLogger; + private Mock mockRoleService; + private Mock mockUrlHelper; + private IDataProtectionProvider mockDataProtectionProvider; + + [SetUp] + public void SetUp() + { + this.mockRepository = new MockRepository(MockBehavior.Strict); + + this.mockLogger = this.mockRepository.Create>(); + this.mockRoleService = this.mockRepository.Create(); + this.mockUrlHelper = this.mockRepository.Create(); + this.mockDataProtectionProvider = new EphemeralDataProtectionProvider(); + } + + private RolesController CreateRolesController() + { + return new RolesController( + this.mockRoleService.Object, + this.mockLogger.Object) + { + Url = this.mockUrlHelper.Object + }; + } + + [Test] + public async Task GetAllRolesReturnOkResult() + { + // Arrange + var roleController = CreateRolesController(); + + var paginedRoles = new PaginatedResult() + { + Data = Enumerable.Range(0, 10).Select(x => new RoleModel() + { + Id = FormattableString.Invariant($"{x}"), + }).ToList(), + TotalCount = 100, + PageSize = 10, + CurrentPage = 0 + }; + + _ = this.mockRoleService + .Setup(x => x.GetRolePage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(paginedRoles); + + var locationUrl = "http://location/roles"; + + _ = this.mockUrlHelper + .Setup(x => x.RouteUrl(It.IsAny())) + .Returns(locationUrl); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await roleController.Get(); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(paginedRoles.Data.Count, result.Items.Count()); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task Get_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var accessControlController = CreateRolesController(); + + _ = this.mockRoleService + .Setup(x => x.GetRolePage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await accessControlController.Get()); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task GetByIdShouldReturnTheCorrespondantRole() + { + // Arrange + var rolesController = CreateRolesController(); + + var roleId = Guid.NewGuid().ToString(); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + _ = this.mockRoleService + .Setup(x => x.GetRolePage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(new PaginatedResult() + { + Data = new List() { new RoleModel() { Id = roleId } }, + TotalCount = 1, + PageSize = 10, + CurrentPage = 0 + }); + + // Act + var result = await rolesController.Get(roleId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom>(result); + + var role = result.Items.First(); + + Assert.IsNotNull(role); + Assert.AreEqual(roleId, role.Id); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task GetRoleDetailsShouldReturnNotFoundForNonExistingGroup() + { + // Arrange + var rolesController = CreateRolesController(); + var roleId = Guid.NewGuid().ToString(); + + _ = this.mockRoleService + .Setup(x => x.GetRoleDetailsAsync(It.IsAny())) + .ReturnsAsync((RoleDetailsModel)null); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Warning, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await rolesController.GetRoleDetails(roleId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task CreateRoleShouldReturnOk() + { + // Arrange + var rolesController = CreateRolesController(); + + var role = new RoleDetailsModel() + { + Id = Guid.NewGuid().ToString() + }; + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockRoleService + .Setup(x => x.CreateRole(It.Is(c => c.Id.Equals(role.Id, StringComparison.Ordinal)))) + .ReturnsAsync(role); + + // Act + var result = await rolesController.CreateRoleAsync(role); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + var okObjectResult = result as ObjectResult; + + Assert.IsNotNull(okObjectResult); + Assert.AreEqual(200, okObjectResult.StatusCode); + + Assert.IsNotNull(okObjectResult.Value); + Assert.IsAssignableFrom(okObjectResult.Value); + + var roleObj = okObjectResult.Value as RoleDetailsModel; + Assert.IsNotNull(roleObj); + Assert.AreEqual(role.Id, roleObj.Id); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task CreateRoleShouldReturnBadRequestWhenCreationFails() + { + // Arrange + var rolesController = CreateRolesController(); + var role = new RoleDetailsModel() + { + Id = Guid.NewGuid().ToString() + }; + + _ = this.mockRoleService + .Setup(x => x.CreateRole(It.IsAny())) + .Throws(new Exception()); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await rolesController.CreateRoleAsync(role); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + + [Test] + public async Task UpdateRoleShouldReturnOkResult() + { + // Arrange + var rolesController = CreateRolesController(); + + + var roleId = Guid.NewGuid().ToString(); + + var role = new RoleDetailsModel() + { + Id = roleId + }; + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockRoleService + .Setup(x => x.UpdateRole(roleId, It.Is(c => c.Id.Equals(role.Id, StringComparison.Ordinal)))) + .ReturnsAsync(role); + + // Act + var result = await rolesController.EditRoleAsync(roleId, role); + + // Assert + Assert.IsNotNull(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task EditRoleAsync_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var rolesController = CreateRolesController(); + var roleId = Guid.NewGuid().ToString(); + var role = new RoleDetailsModel() { Id = roleId }; + + _ = this.mockRoleService + .Setup(x => x.UpdateRole(roleId, It.IsAny())) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await rolesController.EditRoleAsync(roleId, role)); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteRoleShouldReturnExpectedBehavior() + { + // Arrange + var rolesController = CreateRolesController(); + var deviceId = Guid.NewGuid().ToString(); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockRoleService.Setup(c => c.DeleteRole(It.Is(x => x == deviceId))) + .ReturnsAsync(true); + + // Act + var result = await rolesController.DeleteRole(deviceId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteRole_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var rolesController = CreateRolesController(); + var roleId = Guid.NewGuid().ToString(); + + _ = this.mockRoleService.Setup(c => c.DeleteRole(It.Is(x => x == roleId))) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await rolesController.DeleteRole(roleId)); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/SettingsControllerTest.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/SettingsControllerTest.cs index c36e7705c..45e31372d 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/SettingsControllerTest.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/SettingsControllerTest.cs @@ -1,7 +1,7 @@ // Copyright (c) CGI France. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 { using System; using System.Globalization; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/UsersControllerTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/UsersControllerTests.cs new file mode 100644 index 000000000..e2c2a3357 --- /dev/null +++ b/src/IoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/UsersControllerTests.cs @@ -0,0 +1,414 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace IoTHub.Portal.Tests.Unit.Server.Controllers.v10 +{ + using IoTHub.Portal.Application.Services; + using IoTHub.Portal.Server.Controllers.V10; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.Extensions.Logging; + using Moq; + using System.Collections.Generic; + using System.Threading.Tasks; + using NUnit.Framework; + using Microsoft.AspNetCore.DataProtection; + using Microsoft.AspNetCore.Mvc; + using IoTHub.Portal.Models.v10; + using IoTHub.Portal.Shared.Models.v10; + using Microsoft.AspNetCore.Mvc.Routing; + using System.Linq; + using System; + + [TestFixture] + public class UserssControllerTests + { + private MockRepository mockRepository; + + private Mock> mockLogger; + private Mock mockUserService; + private Mock mockAccessControlService; + private Mock mockUrlHelper; + private IDataProtectionProvider mockDataProtectionProvider; + + [SetUp] + public void SetUp() + { + this.mockRepository = new MockRepository(MockBehavior.Strict); + + this.mockLogger = this.mockRepository.Create>(); + this.mockUserService = this.mockRepository.Create(); + this.mockAccessControlService = this.mockRepository.Create(); + this.mockUrlHelper = this.mockRepository.Create(); + this.mockDataProtectionProvider = new EphemeralDataProtectionProvider(); + } + + private UsersController CreateUsersController() + { + return new UsersController( + this.mockUserService.Object, + this.mockLogger.Object, + this.mockAccessControlService.Object) + { + Url = this.mockUrlHelper.Object + }; + } + + [Test] + public async Task GetAllUsersReturnOkResult() + { + // Arrange + var userController = CreateUsersController(); + + var paginedUsers = new PaginatedResult() + { + Data = Enumerable.Range(0, 10).Select(x => new UserModel() + { + Id = FormattableString.Invariant($"{x}"), + }).ToList(), + TotalCount = 100, + PageSize = 10, + CurrentPage = 0 + }; + + _ = this.mockUserService + .Setup(x => x.GetUserPage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(paginedUsers); + + var locationUrl = "http://location/users"; + + _ = this.mockUrlHelper + .Setup(x => x.RouteUrl(It.IsAny())) + .Returns(locationUrl); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await userController.Get(); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(paginedUsers.Data.Count, result.Items.Count()); + + this.mockRepository.VerifyAll(); + } + + + [Test] + public async Task Get_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var usersController = CreateUsersController(); + + _ = this.mockUserService + .Setup(x => x.GetUserPage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await usersController.Get()); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task GetByIdShouldReturnTheCorrespondantUser() + { + // Arrange + var usersController = CreateUsersController(); + + var userId = Guid.NewGuid().ToString(); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + _ = this.mockUserService + .Setup(x => x.GetUserPage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(new PaginatedResult() + { + Data = new List() { new UserModel() { Id = userId } }, + TotalCount = 1, + PageSize = 10, + CurrentPage = 0 + }); + + // Act + var result = await usersController.Get(userId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom>(result); + + var user = result.Items.First(); + + Assert.IsNotNull(user); + Assert.AreEqual(userId, user.Id); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task GetUserDetailsShouldReturnNotFoundForNonExistingGroup() + { + // Arrange + var userController = CreateUsersController(); + var userId = Guid.NewGuid().ToString(); + + _ = this.mockUserService + .Setup(x => x.GetUserDetailsAsync(It.IsAny())) + .ReturnsAsync((UserDetailsModel)null); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Warning, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await userController.GetUserDetails(userId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task CreateUserShouldReturnOk() + { + // Arrange + var usersController = CreateUsersController(); + + var user = new UserDetailsModel() + { + Id = Guid.NewGuid().ToString() + }; + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockUserService + .Setup(x => x.CreateUserAsync(It.Is(c => c.Id.Equals(user.Id, StringComparison.Ordinal)))) + .ReturnsAsync(user); + + // Act + var result = await usersController.CreateUser(user); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + var createdAtActionResult = result as CreatedAtActionResult; + + Assert.IsNotNull(createdAtActionResult); + Assert.AreEqual(201, createdAtActionResult.StatusCode); + + Assert.IsNotNull(createdAtActionResult.Value); + Assert.IsAssignableFrom(createdAtActionResult.Value); + + var userObj = createdAtActionResult.Value as UserDetailsModel; + Assert.IsNotNull(userObj); + Assert.AreEqual(user.Id, userObj.Id); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task CreateUserShouldReturnBadRequestWhenCreationFails() + { + // Arrange + var userController = CreateUsersController(); + var user = new UserDetailsModel() + { + Id = Guid.NewGuid().ToString() + }; + + _ = this.mockUserService + .Setup(x => x.CreateUserAsync(It.IsAny())) + .Throws(new Exception()); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var result = await userController.CreateUser(user); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task UpdateUserShouldReturnOkResult() + { + // Arrange + var usersController = CreateUsersController(); + + + var userId = Guid.NewGuid().ToString(); + + var user = new UserDetailsModel() + { + Id = userId + }; + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Information, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + _ = this.mockUserService + .Setup(x => x.UpdateUser(userId, It.Is(c => c.Id.Equals(user.Id, StringComparison.Ordinal)))) + .ReturnsAsync(user); + + // Act + var result = await usersController.EditUserAsync(userId, user); + + // Assert + Assert.IsNotNull(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task EditUserAsync_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var usersController = CreateUsersController(); + var userId = Guid.NewGuid().ToString(); + var user = new UserDetailsModel() { Id = userId }; + + _ = this.mockUserService + .Setup(x => x.UpdateUser(userId, It.IsAny())) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await usersController.EditUserAsync(userId, user)); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteUserShouldReturnExpectedBehavior() + { + // Arrange + var usersController = CreateUsersController(); + var deviceId = Guid.NewGuid().ToString(); + + _ = this.mockUserService.Setup(c => c.DeleteUser(It.Is(x => x == deviceId))) + .ReturnsAsync(true); + + _ = this.mockLogger.Setup(c => c.Log( + It.Is(x => x == LogLevel.Information), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>())); + + // Act + var result = await usersController.DeleteUser(deviceId); + + // Assert + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result); + + this.mockRepository.VerifyAll(); + } + + [Test] + public async Task DeleteUser_ShouldReturnInternalServerErrorOnException() + { + // Arrange + var usersController = CreateUsersController(); + var userId = Guid.NewGuid().ToString(); + + _ = this.mockUserService.Setup(c => c.DeleteUser(It.Is(x => x == userId))) + .ThrowsAsync(new Exception("Test exception")); + + _ = this.mockLogger.Setup(x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.IsAny(), + (Func)It.IsAny() + )); + + // Act + var ex = Assert.ThrowsAsync(async () => await usersController.DeleteUser(userId)); + + // Assert + Assert.IsNotNull(ex); + Assert.AreEqual("Test exception", ex.Message); + + this.mockRepository.VerifyAll(); + } + } +} diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Services/DeviceModelServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Services/DeviceModelServiceTests.cs index 448d16f30..ae80f8442 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Services/DeviceModelServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Services/DeviceModelServiceTests.cs @@ -18,7 +18,7 @@ namespace IoTHub.Portal.Tests.Unit.Server.Services using IoTHub.Portal.Domain; using IoTHub.Portal.Infrastructure.Mappers; using IoTHub.Portal.Server.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using EntityFramework.Exceptions.Common; using FluentAssertions; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceServiceTest.cs b/src/IoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceServiceTest.cs index 36f067d13..2d3a879a1 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceServiceTest.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceServiceTest.cs @@ -19,7 +19,7 @@ namespace IoTHub.Portal.Tests.Unit.Server.Services using IoTHub.Portal.Infrastructure.Helpers; using IoTHub.Portal.Models.v10; using IoTHub.Portal.Server.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Tests.Unit.UnitTests.Bases; using EntityFramework.Exceptions.Common; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Services/IdeaServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Services/IdeaServiceTests.cs index 6be332020..7f2605f17 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Services/IdeaServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Services/IdeaServiceTests.cs @@ -13,7 +13,7 @@ namespace IoTHub.Portal.Tests.Unit.Server.Services using IoTHub.Portal.Domain; using IoTHub.Portal.Domain.Exceptions; using IoTHub.Portal.Server.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Moq; diff --git a/src/IoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs b/src/IoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs index c005e7cbd..b997b2e1b 100644 --- a/src/IoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs +++ b/src/IoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs @@ -18,7 +18,7 @@ namespace IoTHub.Portal.Tests.Unit.Server.Services using IoTHub.Portal.Infrastructure.Repositories; using IoTHub.Portal.Models.v10.LoRaWAN; using IoTHub.Portal.Server.Services; - using IoTHub.Portal.Shared.Models.v1._0; + using IoTHub.Portal.Shared.Models.v10; using IoTHub.Portal.Shared.Models.v10.Filters; using IoTHub.Portal.Tests.Unit.UnitTests.Bases; using EntityFramework.Exceptions.Common;