This commit is contained in:
Jicheng Lu 2024-11-14 17:33:26 -06:00
parent 9a954d8551
commit dda5c2a211
30 changed files with 481 additions and 80 deletions

View file

@ -14,6 +14,9 @@ public class UserFilter : Pagination
[JsonPropertyName("roles")]
public IEnumerable<string>? Roles { get; set; }
[JsonPropertyName("types")]
public IEnumerable<string>? Types { get; set; }
[JsonPropertyName("sources")]
public IEnumerable<string>? Sources { get; set; }

View file

@ -18,9 +18,10 @@ public interface IBotSharpRepository : IHaveServiceProvider
#endregion
#region Role
bool RefreshRoles(IEnumerable<Role> roles) => throw new NotImplementedException();
IEnumerable<Role> GetRoles(RoleFilter filter) => throw new NotImplementedException();
Role? GetRoleDetails(string roleId) => throw new NotImplementedException();
bool UpdateRole(Role role, bool isUpdateRoleAgents = false) => throw new NotImplementedException();
Role? GetRoleDetails(string roleId, bool includeAgent = false) => throw new NotImplementedException();
bool UpdateRole(Role role, bool updateRoleAgents = false) => throw new NotImplementedException();
#endregion
#region User
@ -41,8 +42,8 @@ public interface IBotSharpRepository : IHaveServiceProvider
void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException();
void UpdateUsersIsDisable(List<string> userIds, bool isDisable) => throw new NotImplementedException();
PagedItems<User> GetUsers(UserFilter filter) => throw new NotImplementedException();
User? GetUserDetails(string userId) => throw new NotImplementedException();
bool UpdateUser(User user, bool isUpdateUserAgents = false) => throw new NotImplementedException();
User? GetUserDetails(string userId, bool includeAgent = false) => throw new NotImplementedException();
bool UpdateUser(User user, bool updateUserAgents = false) => throw new NotImplementedException();
#endregion
#region Agent

View file

@ -5,6 +5,7 @@ namespace BotSharp.Abstraction.Roles;
public interface IRoleService
{
Task<bool> RefreshRoles();
Task<IEnumerable<string>> GetRoleOptions();
Task<IEnumerable<Role>> GetRoles(RoleFilter filter);
Task<Role?> GetRoleDetails(string roleId);

View file

@ -15,8 +15,8 @@ public class Role
public IEnumerable<RoleAgentAction> AgentActions { get; set; } = [];
[JsonPropertyName("updated_time")]
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime UpdatedTime { get; set; }
[JsonPropertyName("created_time")]
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; }
}

View file

@ -18,8 +18,8 @@ public class RoleAgent
public Agent? Agent { get; set; }
[JsonPropertyName("updated_time")]
public DateTime UpdatedTime { get; set; } = DateTime.UtcNow;
public DateTime UpdatedTime { get; set; }
[JsonPropertyName("created_time")]
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
public DateTime CreatedTime { get; set; }
}

View file

@ -4,4 +4,6 @@ public static class UserAction
{
public const string Edit = "edit";
public const string Chat = "chat";
public const string Train = "train";
public const string Evaluate = "evaluate";
}

View file

@ -2,21 +2,23 @@ namespace BotSharp.Abstraction.Users.Enums;
public class UserRole
{
public const string Root = "root";
/// <summary>
/// Admin account
/// </summary>
public const string Admin = "admin";
/// <summary>
/// Customer service representative (CSR)
/// </summary>
public const string CSR = "csr";
/// <summary>
/// Authorized user
/// </summary>
public const string User = "user";
/// <summary>
/// Customer service representative (CSR)
/// </summary>
public const string CSR = "csr";
/// <summary>
/// Back office operations
/// </summary>
@ -33,6 +35,4 @@ public class UserRole
/// AI Assistant
/// </summary>
public const string Assistant = "assistant";
public const string Root = "root";
}

View file

@ -9,6 +9,8 @@ public interface IUserService
Task<User> GetUser(string id);
Task<PagedItems<User>> GetUsers(UserFilter filter);
Task<User?> GetUserDetails(string userId);
Task<bool> IsAuthorizedUser(string userId);
Task<UserAuthorization> GetUserAuthorizations(string? agentId = null);
Task<bool> UpdateUser(User user, bool isUpdateUserAgents = false);
Task<User> CreateUser(User user);
Task<Token> ActiveUser(UserActivationModel model);

View file

@ -0,0 +1,8 @@
namespace BotSharp.Abstraction.Users.Models;
public class UserAuthorization
{
public bool IsAdmin { get; set; }
public IEnumerable<string> Permissions { get; set; } = [];
public IEnumerable<string> AgentActions { get; set; } = [];
}

View file

@ -25,8 +25,11 @@ public partial class AgentService
var agentSettings = _services.GetRequiredService<AgentSettings>();
var user = _db.GetUserById(_user.Id);
var userService = _services.GetRequiredService<IUserService>();
var auth = await userService.GetUserAuthorizations();
_db.BulkInsertAgents(new List<Agent> { agentRecord });
if (!UserConstant.AdminRoles.Contains(user.Role))
if (auth.IsAdmin || auth.Permissions.Contains(UserPermission.CreateAgent))
{
_db.BulkInsertUserAgents(new List<UserAgent>
{
@ -34,7 +37,7 @@ public partial class AgentService
{
UserId = user.Id,
AgentId = agentRecord.Id,
Actions = new List<string> { UserAction.Edit, UserAction.Chat },
Actions = new List<string> { UserAction.Edit, UserAction.Train, UserAction.Evaluate, UserAction.Chat },
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
}

View file

@ -6,11 +6,10 @@ public partial class AgentService
{
public async Task<bool> DeleteAgent(string id)
{
var user = _db.GetUserById(_user.Id);
var userAgents = await GetUserAgents(user?.Id);
var found = userAgents?.FirstOrDefault(x => x.AgentId == id);
var userService = _services.GetRequiredService<IUserService>();
var auth = await userService.GetUserAuthorizations(id);
if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || !found.Actions.Contains(UserAction.Edit)))
if (auth.IsAdmin || auth.AgentActions.Contains(UserAction.Edit))
{
return false;
}

View file

@ -17,8 +17,9 @@ public partial class AgentService
return refreshResult;
}
var user = _db.GetUserById(_user.Id);
if (!UserConstant.AdminRoles.Contains(user.Role))
var userService = _services.GetRequiredService<IUserService>();
var isValid = await userService.IsAuthorizedUser(_user.Id);
if (!isValid)
{
return "Unauthorized user.";
}

View file

@ -12,12 +12,9 @@ public partial class AgentService
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
var auth = await userService.GetUserAuthorizations(agent.Id);
var userAgents = await GetUserAgents(user.Id);
var found = userAgents?.FirstOrDefault(x => x.AgentId == agent.Id);
if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || found.Actions.Contains(UserAction.Edit)))
if (!auth.IsAdmin && !auth.AgentActions.Contains(UserAction.Edit))
{
return;
}

View file

@ -9,6 +9,7 @@ using BotSharp.Abstraction.Users.Settings;
using BotSharp.Abstraction.Interpreters.Settings;
using BotSharp.Abstraction.Infrastructures;
using BotSharp.Core.Processors;
using BotSharp.Core.Roles.Services;
namespace BotSharp.Core;
@ -23,6 +24,7 @@ public static class BotSharpCoreExtensions
services.AddSingleton<DistributedLocker>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<ProcessorFactory>();

View file

@ -528,7 +528,7 @@ namespace BotSharp.Core.Repository
var agentDir = GetAgentDataDir(agentId);
if (string.IsNullOrEmpty(agentDir)) return false;
// Delete agent user relationships
// Delete user agents
var usersDir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER);
if (Directory.Exists(usersDir))
{
@ -546,6 +546,24 @@ namespace BotSharp.Core.Repository
}
}
// Delete role agents
var rolesDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
if (Directory.Exists(rolesDir))
{
foreach (var roleDir in Directory.GetDirectories(rolesDir))
{
var roleAgentFile = Directory.GetFiles(roleDir).FirstOrDefault(x => Path.GetFileName(x) == ROLE_AGENT_FILE);
if (string.IsNullOrEmpty(roleAgentFile)) continue;
var text = File.ReadAllText(roleAgentFile);
var roleAgents = JsonSerializer.Deserialize<List<RoleAgent>>(text, _options);
if (roleAgents.IsNullOrEmpty()) continue;
roleAgents = roleAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? [];
File.WriteAllText(roleAgentFile, JsonSerializer.Serialize(roleAgents, _options));
}
}
// Delete agent folder
Directory.Delete(agentDir, true);
Reset();
@ -561,6 +579,7 @@ namespace BotSharp.Core.Repository
{
_agents = [];
_userAgents = [];
_roleAgents = [];
}
}
}

View file

@ -1,10 +1,39 @@
using BotSharp.Abstraction.Users.Models;
using System.IO;
namespace BotSharp.Core.Repository;
public partial class FileRepository
{
public bool RefreshRoles(IEnumerable<Role> roles)
{
if (roles.IsNullOrEmpty()) return false;
var validRoles = roles.Where(x => !string.IsNullOrWhiteSpace(x.Id)
&& !string.IsNullOrWhiteSpace(x.Name)).ToList();
if (validRoles.IsNullOrEmpty()) return false;
var baseDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
if (Directory.Exists(baseDir))
{
Directory.Delete(baseDir, true);
}
Directory.CreateDirectory(baseDir);
foreach (var role in validRoles)
{
var dir = Path.Combine(baseDir, role.Id);
Directory.CreateDirectory(dir);
Thread.Sleep(50);
var roleFile = Path.Combine(dir, ROLE_FILE);
role.CreatedTime = DateTime.UtcNow;
role.UpdatedTime = DateTime.UtcNow;
File.WriteAllText(roleFile, JsonSerializer.Serialize(role, _options));
}
return true;
}
public IEnumerable<Role> GetRoles(RoleFilter filter)
{
var roles = Roles;
@ -22,7 +51,7 @@ public partial class FileRepository
return roles.ToList();
}
public Role? GetRoleDetails(string roleId)
public Role? GetRoleDetails(string roleId, bool includeAgent = false)
{
if (string.IsNullOrWhiteSpace(roleId)) return null;
@ -31,8 +60,20 @@ public partial class FileRepository
var agentActions = new List<RoleAgentAction>();
var roleAgents = RoleAgents?.Where(x => x.RoleId == roleId)?.ToList() ?? [];
var agentIds = roleAgents.Select(x => x.AgentId).Distinct().ToList();
if (!includeAgent)
{
agentActions = roleAgents.Select(x => new RoleAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Actions = x.Actions
}).ToList();
role.AgentActions = agentActions;
return role;
}
var agentIds = roleAgents.Select(x => x.AgentId).Distinct().ToList();
if (!agentIds.IsNullOrEmpty())
{
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
@ -56,7 +97,7 @@ public partial class FileRepository
return role;
}
public bool UpdateRole(Role role, bool isUpdateRoleAgents = false)
public bool UpdateRole(Role role, bool updateRoleAgents = false)
{
if (string.IsNullOrEmpty(role?.Id) || string.IsNullOrEmpty(role?.Name))
{
@ -74,7 +115,7 @@ public partial class FileRepository
role.UpdatedTime = DateTime.UtcNow;
File.WriteAllText(roleFile, JsonSerializer.Serialize(role, _options));
if (isUpdateRoleAgents)
if (updateRoleAgents)
{
var roleAgents = role.AgentActions?.Select(x => new RoleAgent
{

View file

@ -1,7 +1,5 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
using System;
using System.IO;
namespace BotSharp.Core.Repository;
@ -97,6 +95,10 @@ public partial class FileRepository
{
users = users.Where(x => filter.Roles.Contains(x.Role));
}
if (!filter.Types.IsNullOrEmpty())
{
users = users.Where(x => filter.Types.Contains(x.Type));
}
if (!filter.Sources.IsNullOrEmpty())
{
users = users.Where(x => filter.Sources.Contains(x.Source));
@ -109,17 +111,29 @@ public partial class FileRepository
};
}
public User? GetUserDetails(string userId)
public User? GetUserDetails(string userId, bool includeAgent = false)
{
if (string.IsNullOrWhiteSpace(userId)) return null;
var user = Users.FirstOrDefault(x => x.Id == userId);
var user = Users.FirstOrDefault(x => x.Id == userId || x.ExternalId == userId);
if (user == null) return null;
var agentActions = new List<UserAgentAction>();
var userAgents = UserAgents?.Where(x => x.UserId == userId)?.ToList() ?? [];
var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList();
if (!includeAgent)
{
agentActions = userAgents.Select(x => new UserAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Actions = x.Actions
}).ToList();
user.AgentActions = agentActions;
return user;
}
var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList();
if (!agentIds.IsNullOrEmpty())
{
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
@ -143,7 +157,7 @@ public partial class FileRepository
return user;
}
public bool UpdateUser(User user, bool isUpdateUserAgents = false)
public bool UpdateUser(User user, bool updateUserAgents = false)
{
if (string.IsNullOrEmpty(user?.Id)) return false;
@ -157,7 +171,7 @@ public partial class FileRepository
user.UpdatedTime = DateTime.UtcNow;
File.WriteAllText(userFile, JsonSerializer.Serialize(user, _options));
if (isUpdateUserAgents)
if (updateUserAgents)
{
var userAgents = user.AgentActions?.Select(x => new UserAgent
{

View file

@ -16,6 +16,15 @@ public class RoleService : IRoleService
_logger = logger;
}
public async Task<bool> RefreshRoles()
{
var allRoles = await GetRoleOptions();
var roles = allRoles.Select(x => new Role { Id = Guid.NewGuid().ToString(), Name = x }).ToList();
var db = _services.GetRequiredService<IBotSharpRepository>();
return db.RefreshRoles(roles);
}
public async Task<IEnumerable<string>> GetRoleOptions()
{
var fields = typeof(UserRole).GetFields(BindingFlags.Public | BindingFlags.Static)
@ -37,7 +46,7 @@ public class RoleService : IRoleService
public async Task<Role?> GetRoleDetails(string roleId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var role = db.GetRoleDetails(roleId);
var role = db.GetRoleDetails(roleId, includeAgent: true);
return role;
}

View file

@ -407,10 +407,54 @@ public class UserService : IUserService
return users;
}
public async Task<bool> IsAuthorizedUser(string userId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var user = db.GetUserById(userId);
return user != null && UserConstant.AdminRoles.Contains(user.Role);
}
public async Task<UserAuthorization> GetUserAuthorizations(string? agentId = null)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var user = db.GetUserById(_user.Id);
var auth = new UserAuthorization();
if (user == null) return auth;
var permissions = user.Permissions;
var role = db.GetRoles(new RoleFilter { Names = [ user.Role ] }).FirstOrDefault();
if (role != null && !permissions.Any())
{
permissions = role.Permissions ?? [];
}
auth.IsAdmin = UserConstant.AdminRoles.Contains(user.Role);
auth.Permissions = permissions;
if (string.IsNullOrEmpty(agentId))
{
return auth;
}
var userAgent = db.GetUserDetails(user.Id)?.AgentActions?.FirstOrDefault(x => x.AgentId == agentId);
var actions = userAgent?.Actions ?? [];
if (role != null && !actions.Any())
{
var roleAgent = db.GetRoleDetails(role.Id)?.AgentActions?.FirstOrDefault(x => x.AgentId == agentId);
actions = roleAgent?.Actions ?? [];
}
auth.AgentActions = actions;
return auth;
}
public async Task<User?> GetUserDetails(string userId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
return db.GetUserDetails(userId);
return db.GetUserDetails(userId, includeAgent: true);
}
public async Task<bool> UpdateUser(User user, bool isUpdateUserAgents = false)

View file

@ -58,20 +58,11 @@ public class AgentController : ControllerBase
rule.RedirectToAgentName = found.Name;
}
var editable = true;
var chatable = true;
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (!UserConstant.AdminRoles.Contains(user?.Role))
{
var userAgents = await _agentService.GetUserAgents(user?.Id);
var actions = userAgents?.FirstOrDefault(x => x.AgentId == targetAgent.Id)?.Actions ?? [];
editable = actions.Contains(UserAction.Edit);
chatable = actions.Contains(UserAction.Chat);
}
var auth = await userService.GetUserAuthorizations(targetAgent.Id);
targetAgent.Editable = editable;
targetAgent.Chatable = chatable;
targetAgent.Editable = auth.IsAdmin || auth.AgentActions.Contains(UserAction.Edit);
targetAgent.Chatable = auth.IsAdmin || auth.AgentActions.Contains(UserAction.Chat);
return targetAgent;
}

View file

@ -22,9 +22,8 @@ public class PluginController : ControllerBase
[HttpGet("/plugins")]
public async Task<PagedItems<PluginDef>> GetPlugins([FromQuery] PluginFilter filter)
{
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (!UserConstant.AdminRoles.Contains(user?.Role))
var isValid = await IsValidUser();
if (!isValid)
{
return new PagedItems<PluginDef>();
}
@ -55,7 +54,11 @@ public class PluginController : ControllerBase
{
Roles = new List<string> { UserRole.Root, UserRole.Admin }
},
new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 33)
new PluginMenuDef("Roles", link: "page/roles", icon: "bx bx-group", weight: 33)
{
Roles = new List<string> { UserRole.Root, UserRole.Admin }
},
new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 34)
{
Roles = new List<string> { UserRole.Root, UserRole.Admin }
}
@ -91,4 +94,10 @@ public class PluginController : ControllerBase
var loader = _services.GetRequiredService<PluginLoader>();
return loader.UpdatePluginStatus(_services, id, false);
}
private async Task<bool> IsValidUser()
{
var userService = _services.GetRequiredService<IUserService>();
return await userService.IsAuthorizedUser(_user.Id);
}
}

View file

@ -21,6 +21,19 @@ public class RoleController : ControllerBase
_user = user;
}
[HttpPost("/role/refresh")]
public async Task<bool> RefreshRoles()
{
var isValid = await IsValidUser();
if (!isValid)
{
return false;
}
return await _roleService.RefreshRoles();
}
[HttpGet("/role/options")]
public async Task<IEnumerable<string>> GetRoleOptions()
{
@ -35,6 +48,12 @@ public class RoleController : ControllerBase
filter = RoleFilter.Empty();
}
var isValid = await IsValidUser();
if (!isValid)
{
return Enumerable.Empty<RoleViewModel>();
}
var roles = await _roleService.GetRoles(filter);
return roles.Select(x => RoleViewModel.FromRole(x)).ToList();
}
@ -51,9 +70,8 @@ public class RoleController : ControllerBase
{
if (model == null) return false;
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
var isValid = await IsValidUser();
if (!isValid)
{
return false;
}
@ -61,4 +79,10 @@ public class RoleController : ControllerBase
var role = RoleUpdateModel.ToRole(model);
return await _roleService.UpdateRole(role, isUpdateRoleAgents: true);
}
private async Task<bool> IsValidUser()
{
var userService = _services.GetRequiredService<IUserService>();
return await userService.IsAuthorizedUser(_user.Id);
}
}

View file

@ -182,8 +182,8 @@ public class UserController : ControllerBase
public async Task<PagedItems<UserViewModel>> GetUsers([FromBody] UserFilter filter)
{
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
var isValid = await IsValidUser();
if (!isValid)
{
return new PagedItems<UserViewModel>();
}
@ -211,13 +211,13 @@ public class UserController : ControllerBase
{
if (model == null) return false;
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
var isValid = await IsValidUser();
if (!isValid)
{
return false;
}
var userService = _services.GetRequiredService<IUserService>();
var updated = await userService.UpdateUser(UserUpdateModel.ToUser(model), isUpdateUserAgents: true);
return updated;
}
@ -252,6 +252,12 @@ public class UserController : ControllerBase
#region Private methods
private async Task<bool> IsValidUser()
{
var userService = _services.GetRequiredService<IUserService>();
return await userService.IsAuthorizedUser(_user.Id);
}
private FileContentResult BuildFileResult(string file)
{
var fileStorage = _services.GetRequiredService<IFileStorageService>();

View file

@ -18,10 +18,10 @@ public class RoleViewModel
public IEnumerable<RoleAgentActionViewModel> AgentActions { get; set; } = [];
[JsonPropertyName("create_date")]
public DateTime CreateDate { get; set; }
public DateTime? CreateDate { get; set; }
[JsonPropertyName("update_date")]
public DateTime UpdateDate { get; set; }
public DateTime? UpdateDate { get; set; }
public static RoleViewModel FromRole(Role? role)
{
@ -33,8 +33,8 @@ public class RoleViewModel
Name = role.Name,
Permissions = role.Permissions,
AgentActions = role.AgentActions?.Select(x => RoleAgentActionViewModel.ToViewModel(x)) ?? [],
CreateDate = role.CreatedTime,
UpdateDate = role.UpdatedTime
CreateDate = role.CreatedTime != default ? role.CreatedTime : null,
UpdateDate = role.UpdatedTime != default ? role.UpdatedTime : null
};
}
}

View file

@ -0,0 +1,25 @@
using BotSharp.Abstraction.Roles.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class RoleAgentDocument : MongoBase
{
public string RoleId { get; set; }
public string AgentId { get; set; }
public IEnumerable<string> Actions { get; set; } = [];
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public RoleAgent ToRoleAgent()
{
return new RoleAgent
{
Id = Id,
RoleId = RoleId,
AgentId = AgentId,
Actions = Actions,
CreatedTime = CreatedTime,
UpdatedTime = UpdatedTime
};
}
}

View file

@ -0,0 +1,24 @@
using BotSharp.Abstraction.Roles.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class RoleDocument : MongoBase
{
public string Name { get; set; }
public IEnumerable<string> Permissions { get; set; } = [];
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public Role ToRole()
{
return new Role
{
Id = Id,
Name = Name,
Permissions = Permissions,
CreatedTime = CreatedTime,
UpdatedTime = UpdatedTime
};
}
}

View file

@ -159,4 +159,10 @@ public class MongoDbContext
public IMongoCollection<KnowledgeCollectionFileMetaDocument> KnowledgeCollectionFileMeta
=> Database.GetCollection<KnowledgeCollectionFileMetaDocument>($"{_collectionPrefix}_KnowledgeCollectionFileMeta");
public IMongoCollection<RoleDocument> Roles
=> Database.GetCollection<RoleDocument>($"{_collectionPrefix}_Roles");
public IMongoCollection<RoleAgentDocument> RoleAgents
=> Database.GetCollection<RoleAgentDocument>($"{_collectionPrefix}_RoleAgents");
}

View file

@ -2,6 +2,7 @@ using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Models;
using MongoDB.Driver;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -332,8 +333,6 @@ public partial class MongoRepository
if (found.IsNullOrEmpty()) return [];
var agentIds = found.Select(x => x.AgentId).Distinct().ToList();
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
var res = found.Select(x => new UserAgent
{
Id = x.Id,
@ -344,6 +343,8 @@ public partial class MongoRepository
UpdatedTime = x.UpdatedTime
}).ToList();
var agentIds = found.Select(x => x.AgentId).Distinct().ToList();
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
foreach (var item in res)
{
var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
@ -455,6 +456,7 @@ public partial class MongoRepository
try
{
_dc.UserAgents.DeleteMany(Builders<UserAgentDocument>.Filter.Empty);
_dc.RoleAgents.DeleteMany(Builders<RoleAgentDocument>.Filter.Empty);
_dc.Agents.DeleteMany(Builders<AgentDocument>.Filter.Empty);
return true;
}
@ -472,10 +474,12 @@ public partial class MongoRepository
var agentFilter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
var userAgentFilter = Builders<UserAgentDocument>.Filter.Eq(x => x.AgentId, agentId);
var roleAgentFilter = Builders<RoleAgentDocument>.Filter.Eq(x => x.AgentId, agentId);
var agentTaskFilter = Builders<AgentTaskDocument>.Filter.Eq(x => x.AgentId, agentId);
_dc.Agents.DeleteOne(agentFilter);
_dc.UserAgents.DeleteMany(userAgentFilter);
_dc.RoleAgents.DeleteMany(roleAgentFilter);
_dc.AgentTasks.DeleteMany(agentTaskFilter);
return true;
}

View file

@ -0,0 +1,152 @@
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Roles.Models;
namespace BotSharp.Plugin.MongoStorage.Repository;
public partial class MongoRepository
{
public bool RefreshRoles(IEnumerable<Role> roles)
{
if (roles.IsNullOrEmpty()) return false;
var validRoles = roles.Where(x => !string.IsNullOrWhiteSpace(x.Id)
&& !string.IsNullOrWhiteSpace(x.Name)).ToList();
if (validRoles.IsNullOrEmpty()) return false;
// Clear data
_dc.RoleAgents.DeleteMany(Builders<RoleAgentDocument>.Filter.Empty);
_dc.Roles.DeleteMany(Builders<RoleDocument>.Filter.Empty);
var roleDocs = validRoles.Select(x => new RoleDocument
{
Id = x.Id,
Name = x.Name,
Permissions = x.Permissions,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
});
_dc.Roles.InsertMany(roleDocs);
return true;
}
public IEnumerable<Role> GetRoles(RoleFilter filter)
{
if (filter == null)
{
filter = RoleFilter.Empty();
}
var roleBuilder = Builders<RoleDocument>.Filter;
var roleFilters = new List<FilterDefinition<RoleDocument>>() { roleBuilder.Empty };
// Apply filters
if (!filter.Names.IsNullOrEmpty())
{
roleFilters.Add(roleBuilder.In(x => x.Name, filter.Names));
}
// Search
var roleDocs = _dc.Roles.Find(roleBuilder.And(roleFilters)).ToList();
var roles = roleDocs.Select(x => x.ToRole()).ToList();
return roles;
}
public Role? GetRoleDetails(string roleId, bool includeAgent = false)
{
if (string.IsNullOrWhiteSpace(roleId)) return null;
var roleDoc = _dc.Roles.Find(Builders<RoleDocument>.Filter.Eq(x => x.Id, roleId)).FirstOrDefault();
if (roleDoc == null) return null;
var agentActions = new List<RoleAgentAction>();
var role = roleDoc.ToRole();
var roleAgentDocs = _dc.RoleAgents.Find(Builders<RoleAgentDocument>.Filter.Eq(x => x.RoleId, roleId)).ToList();
if (!includeAgent)
{
agentActions = roleAgentDocs.Select(x => new RoleAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Actions = x.Actions
}).ToList();
role.AgentActions = agentActions;
return role;
}
var agentIds = roleAgentDocs.Select(x => x.AgentId).Distinct().ToList();
if (!agentIds.IsNullOrEmpty())
{
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
foreach (var item in roleAgentDocs)
{
var found = agents.FirstOrDefault(x => x.Id == item.AgentId);
if (found == null) continue;
agentActions.Add(new RoleAgentAction
{
Id = item.Id,
AgentId = found.Id,
Agent = found,
Actions = item.Actions
});
}
}
role.AgentActions = agentActions;
return role;
}
public bool UpdateRole(Role role, bool updateRoleAgents = false)
{
if (string.IsNullOrEmpty(role?.Id)) return false;
var roleFilter = Builders<RoleDocument>.Filter.Eq(x => x.Id, role.Id);
var roleUpdate = Builders<RoleDocument>.Update
.Set(x => x.Name, role.Name)
.Set(x => x.Permissions, role.Permissions)
.Set(x => x.CreatedTime, DateTime.UtcNow)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Roles.UpdateOne(roleFilter, roleUpdate, _options);
if (updateRoleAgents)
{
var roleAgentDocs = role.AgentActions?.Select(x => new RoleAgentDocument
{
Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(),
RoleId = role.Id,
AgentId = x.AgentId,
Actions = x.Actions,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
})?.ToList() ?? [];
var toDelete = _dc.RoleAgents.Find(Builders<RoleAgentDocument>.Filter.And(
Builders<RoleAgentDocument>.Filter.Eq(x => x.RoleId, role.Id),
Builders<RoleAgentDocument>.Filter.Nin(x => x.Id, roleAgentDocs.Select(x => x.Id))
)).ToList();
_dc.RoleAgents.DeleteMany(Builders<RoleAgentDocument>.Filter.In(x => x.Id, toDelete.Select(x => x.Id)));
foreach (var doc in roleAgentDocs)
{
var roleAgentFilter = Builders<RoleAgentDocument>.Filter.Eq(x => x.Id, doc.Id);
var roleAgentUpdate = Builders<RoleAgentDocument>.Update
.Set(x => x.Id, doc.Id)
.Set(x => x.RoleId, role.Id)
.Set(x => x.AgentId, doc.AgentId)
.Set(x => x.Actions, doc.Actions)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.RoleAgents.UpdateOne(roleAgentFilter, roleAgentUpdate, _options);
}
}
return true;
}
}

View file

@ -200,6 +200,10 @@ public partial class MongoRepository
{
userFilters.Add(userBuilder.In(x => x.Role, filter.Roles));
}
if (!filter.Types.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.Type, filter.Types));
}
if (!filter.Sources.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.Source, filter.Sources));
@ -221,15 +225,15 @@ public partial class MongoRepository
};
}
public User? GetUserDetails(string userId)
public User? GetUserDetails(string userId, bool includeAgent = false)
{
if (string.IsNullOrWhiteSpace(userId)) return null;
var userDoc = _dc.Users.Find(Builders<UserDocument>.Filter.Eq(x => x.Id, userId)).FirstOrDefault();
var userDoc = _dc.Users.AsQueryable().FirstOrDefault(x => x.Id == userId || x.ExternalId == userId);
if (userDoc == null) return null;
var agentActions = new List<UserAgentAction>();
var user = userDoc.ToUser();
var userAgents = _dc.UserAgents.AsQueryable().Where(x => x.UserId == userId).Select(x => new UserAgent
{
Id = x.Id,
@ -238,9 +242,19 @@ public partial class MongoRepository
Actions = x.Actions ?? Enumerable.Empty<string>()
}).ToList();
var agentActions = new List<UserAgentAction>();
if (!includeAgent)
{
agentActions = userAgents.Select(x => new UserAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Actions = x.Actions
}).ToList();
user.AgentActions = agentActions;
return user;
}
var agentIds = userAgents.Select(x => x.AgentId)?.Distinct().ToList();
if (!agentIds.IsNullOrEmpty())
{
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
@ -264,7 +278,7 @@ public partial class MongoRepository
return user;
}
public bool UpdateUser(User user, bool isUpdateUserAgents = false)
public bool UpdateUser(User user, bool updateUserAgents = false)
{
if (string.IsNullOrEmpty(user?.Id)) return false;
@ -277,7 +291,7 @@ public partial class MongoRepository
_dc.Users.UpdateOne(userFilter, userUpdate);
if (isUpdateUserAgents)
if (updateUserAgents)
{
var userAgentDocs = user.AgentActions?.Select(x => new UserAgentDocument
{