// (c) Copyright Ascensio System SIA 2010-2022 // // This program is a free software product. // You can redistribute it and/or modify it under the terms // of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software // Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended // to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of // any third-party rights. // // This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see // the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html // // You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. // // The interactive user interfaces in modified source and object code versions of the Program must // display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. // // Pursuant to Section 7(b) of the License you must retain the original Product logo when // distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under // trademark law for use of our trademarks. // // All the Product's GUI elements, including illustrations and icon sets, as well as technical writing // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode using Constants = ASC.Core.Users.Constants; namespace ASC.Core; [Singletone] public class UserManagerConstants { public IDictionary SystemUsers { get; } internal Constants Constants { get; } public UserManagerConstants(Constants constants) { SystemUsers = Configuration.Constants.SystemAccounts.ToDictionary(a => a.ID, a => new UserInfo { Id = a.ID, LastName = a.Name }); SystemUsers[Constants.LostUser.Id] = Constants.LostUser; SystemUsers[Constants.OutsideUser.Id] = Constants.OutsideUser; SystemUsers[constants.NamingPoster.Id] = constants.NamingPoster; Constants = constants; } } [Scope] public class UserManager { private IDictionary SystemUsers => _userManagerConstants.SystemUsers; private readonly IHttpContextAccessor _accessor; private readonly IUserService _userService; private readonly TenantManager _tenantManager; private readonly PermissionContext _permissionContext; private readonly UserManagerConstants _userManagerConstants; private readonly CoreBaseSettings _coreBaseSettings; private readonly CoreSettings _coreSettings; private readonly InstanceCrypto _instanceCrypto; private readonly RadicaleClient _radicaleClient; private readonly CardDavAddressbook _cardDavAddressbook; private readonly ILogger _log; private readonly ICache _cache; private readonly Constants _constants; private Tenant _tenant; private Tenant Tenant => _tenant ??= _tenantManager.GetCurrentTenant(); public UserManager() { } public UserManager( IUserService service, TenantManager tenantManager, PermissionContext permissionContext, UserManagerConstants userManagerConstants, CoreBaseSettings coreBaseSettings, CoreSettings coreSettings, InstanceCrypto instanceCrypto, RadicaleClient radicaleClient, CardDavAddressbook cardDavAddressbook, ILogger log, ICache cache) { _userService = service; _tenantManager = tenantManager; _permissionContext = permissionContext; _userManagerConstants = userManagerConstants; _coreBaseSettings = coreBaseSettings; _coreSettings = coreSettings; _instanceCrypto = instanceCrypto; _radicaleClient = radicaleClient; _cardDavAddressbook = cardDavAddressbook; _log = log; _cache = cache; _constants = _userManagerConstants.Constants; } public UserManager( IUserService service, TenantManager tenantManager, PermissionContext permissionContext, UserManagerConstants userManagerConstants, CoreBaseSettings coreBaseSettings, CoreSettings coreSettings, InstanceCrypto instanceCrypto, RadicaleClient radicaleClient, CardDavAddressbook cardDavAddressbook, ILogger log, ICache cache, IHttpContextAccessor httpContextAccessor) : this(service, tenantManager, permissionContext, userManagerConstants, coreBaseSettings, coreSettings, instanceCrypto, radicaleClient, cardDavAddressbook, log, cache) { _accessor = httpContextAccessor; } public void ClearCache() { if (_userService is ICachedService service) { service.InvalidateCache(); } } #region Users public UserInfo[] GetUsers() { return GetUsers(EmployeeStatus.Default); } public UserInfo[] GetUsers(EmployeeStatus status) { return GetUsers(status, EmployeeType.All); } public UserInfo[] GetUsers(EmployeeStatus status, EmployeeType type) { var users = GetUsersInternal().Where(u => (u.Status & status) == u.Status); switch (type) { case EmployeeType.User: users = users.Where(u => !this.IsVisitor(u)); break; case EmployeeType.Visitor: users = users.Where(u => this.IsVisitor(u)); break; } return users.ToArray(); } public IQueryable GetUsers( bool isAdmin, EmployeeStatus? employeeStatus, List> includeGroups, List excludeGroups, EmployeeActivationStatus? activationStatus, string text, string sortBy, bool sortOrderAsc, long limit, long offset, out int total, out int count) { return _userService.GetUsers(Tenant.Id, isAdmin, employeeStatus, includeGroups, excludeGroups, activationStatus, text, sortBy, sortOrderAsc, limit, offset, out total, out count); } public string[] GetUserNames(EmployeeStatus status) { return GetUsers(status) .Select(u => u.UserName) .Where(s => !string.IsNullOrEmpty(s)) .ToArray(); } public UserInfo GetUserByUserName(string username) { var u = _userService.GetUserByUserName(_tenantManager.GetCurrentTenant().Id, username); return u ?? Constants.LostUser; } public UserInfo GetUserBySid(string sid) { return GetUsersInternal() .FirstOrDefault(u => u.Sid != null && string.Equals(u.Sid, sid, StringComparison.CurrentCultureIgnoreCase)) ?? Constants.LostUser; } public UserInfo GetSsoUserByNameId(string nameId) { return GetUsersInternal() .FirstOrDefault(u => !string.IsNullOrEmpty(u.SsoNameId) && string.Equals(u.SsoNameId, nameId, StringComparison.CurrentCultureIgnoreCase)) ?? Constants.LostUser; } public bool IsUserNameExists(string username) { return GetUserNames(EmployeeStatus.All) .Contains(username, StringComparer.CurrentCultureIgnoreCase); } public UserInfo GetUsers(Guid id) { if (IsSystemUser(id)) { return SystemUsers[id]; } var u = _userService.GetUser(Tenant.Id, id); return u != null && !u.Removed ? u : Constants.LostUser; } public UserInfo GetUser(Guid id, Expression> exp) { if (IsSystemUser(id)) { return SystemUsers[id]; } var u = _userService.GetUser(Tenant.Id, id, exp); return u != null && !u.Removed ? u : Constants.LostUser; } public UserInfo GetUsersByPasswordHash(int tenant, string login, string passwordHash) { var u = _userService.GetUserByPasswordHash(tenant, login, passwordHash); return u != null && !u.Removed ? u : Constants.LostUser; } public bool UserExists(Guid id) { return UserExists(GetUsers(id)); } public bool UserExists(UserInfo user) { return !user.Equals(Constants.LostUser); } public bool IsSystemUser(Guid id) { return SystemUsers.ContainsKey(id); } public UserInfo GetUserByEmail(string email) { if (string.IsNullOrEmpty(email)) { return Constants.LostUser; } var u = _userService.GetUser(Tenant.Id, email); return u != null && !u.Removed ? u : Constants.LostUser; } public UserInfo[] Search(string text, EmployeeStatus status) { return Search(text, status, Guid.Empty); } public UserInfo[] Search(string text, EmployeeStatus status, Guid groupId) { if (text == null || text.Trim().Length == 0) { return new UserInfo[0]; } var words = text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (words.Length == 0) { return Array.Empty(); } var users = groupId == Guid.Empty ? GetUsers(status) : GetUsersByGroup(groupId).Where(u => (u.Status & status) == status); var findUsers = new List(); foreach (var user in users) { var properties = new string[] { user.LastName ?? string.Empty, user.FirstName ?? string.Empty, user.Title ?? string.Empty, user.Location ?? string.Empty, user.Email ?? string.Empty, }; if (IsPropertiesContainsWords(properties, words)) { findUsers.Add(user); } } return findUsers.ToArray(); } public UserInfo SaveUserInfo(UserInfo u, bool isVisitor = false, bool syncCardDav = false) { if (IsSystemUser(u.Id)) { return SystemUsers[u.Id]; } if (u.Id == Guid.Empty) { _permissionContext.DemandPermissions(Constants.Action_AddRemoveUser); } else { _permissionContext.DemandPermissions(new UserSecurityProvider(u.Id), Constants.Action_EditUser); } if (!_coreBaseSettings.Personal) { if (_constants.MaxEveryoneCount <= GetUsersByGroup(Constants.GroupEveryone.ID).Length) { throw new TenantQuotaException("Maximum number of users exceeded"); } if (u.Status == EmployeeStatus.Active) { if (isVisitor) { var maxUsers = _tenantManager.GetTenantQuota(_tenantManager.GetCurrentTenant().Id).ActiveUsers; var visitors = _tenantManager.GetTenantQuota(_tenantManager.GetCurrentTenant().Id).Free ? 0 : _constants.CoefficientOfVisitors; if (!_coreBaseSettings.Standalone && GetUsersByGroup(Constants.GroupVisitor.ID).Length > visitors * maxUsers) { throw new TenantQuotaException("Maximum number of visitors exceeded"); } } else { var q = _tenantManager.GetTenantQuota(_tenantManager.GetCurrentTenant().Id); if (q.ActiveUsers < GetUsersByGroup(Constants.GroupUser.ID).Length) { throw new TenantQuotaException(string.Format("Exceeds the maximum active users ({0})", q.ActiveUsers)); } } } } if (u.Status == EmployeeStatus.Terminated && u.Id == _tenantManager.GetCurrentTenant().OwnerId) { throw new InvalidOperationException("Can not disable tenant owner."); } var oldUserData = _userService.GetUserByUserName(_tenantManager.GetCurrentTenant().Id, u.UserName); var newUser = _userService.SaveUser(_tenantManager.GetCurrentTenant().Id, u); if (syncCardDav) { var tenant = _tenantManager.GetCurrentTenant(); var myUri = (_accessor?.HttpContext != null) ? _accessor.HttpContext.Request.GetUrlRewriter().ToString() : (_cache.Get("REWRITE_URL" + tenant.Id) != null) ? new Uri(_cache.Get("REWRITE_URL" + tenant.Id)).ToString() : tenant.GetTenantDomain(_coreSettings); var rootAuthorization = _cardDavAddressbook.GetSystemAuthorization(); var allUserEmails = GetDavUserEmails().ToList(); if (oldUserData != null && oldUserData.Status != newUser.Status && newUser.Status == EmployeeStatus.Terminated) { var userAuthorization = oldUserData.Email.ToLower() + ":" + _instanceCrypto.Encrypt(oldUserData.Email); var requestUrlBook = _cardDavAddressbook.GetRadicaleUrl(myUri, newUser.Email.ToLower(), true, true); var collection = _cardDavAddressbook.GetCollection(requestUrlBook, userAuthorization, myUri.ToString()).Result; if (collection.Completed && collection.StatusCode != 404) { _cardDavAddressbook.Delete(myUri, newUser.Id, newUser.Email, tenant.Id).Wait();//TODO } foreach (var email in allUserEmails) { var requestUrlItem = _cardDavAddressbook.GetRadicaleUrl(myUri.ToString(), email.ToLower(), true, true, itemID: newUser.Id.ToString()); try { var davItemRequest = new DavRequest() { Url = requestUrlItem, Authorization = rootAuthorization, Header = myUri }; _radicaleClient.RemoveAsync(davItemRequest).ConfigureAwait(false); } catch (Exception ex) { _log.ErrorWithException(ex); } } } else { try { var cardDavUser = new CardDavItem(u.Id, u.FirstName, u.LastName, u.UserName, u.BirthDate, u.Sex, u.Title, u.Email, u.ContactsList, u.MobilePhone); try { _cardDavAddressbook.UpdateItemForAllAddBooks(allUserEmails, myUri, cardDavUser, _tenantManager.GetCurrentTenant().Id, oldUserData != null && oldUserData.Email != newUser.Email ? oldUserData.Email : null).Wait(); // todo } catch (Exception ex) { _log.ErrorWithException(ex); } } catch (Exception ex) { _log.ErrorWithException(ex); } } } return newUser; } public IEnumerable GetDavUserEmails() { return _userService.GetDavUserEmails(_tenantManager.GetCurrentTenant().Id); } public void DeleteUser(Guid id) { if (IsSystemUser(id)) { return; } _permissionContext.DemandPermissions(Constants.Action_AddRemoveUser); if (id == Tenant.OwnerId) { throw new InvalidOperationException("Can not remove tenant owner."); } var delUser = GetUsers(id); _userService.RemoveUser(Tenant.Id, id); var tenant = _tenantManager.GetCurrentTenant(); try { var curreMail = delUser.Email.ToLower(); var currentAccountPaswd = _instanceCrypto.Encrypt(curreMail); var userAuthorization = curreMail + ":" + currentAccountPaswd; var rootAuthorization = _cardDavAddressbook.GetSystemAuthorization(); var myUri = (_accessor?.HttpContext != null) ? _accessor.HttpContext.Request.GetUrlRewriter().ToString() : (_cache.Get("REWRITE_URL" + tenant.Id) != null) ? new Uri(_cache.Get("REWRITE_URL" + tenant.Id)).ToString() : tenant.GetTenantDomain(_coreSettings); var davUsersEmails = GetDavUserEmails(); var requestUrlBook = _cardDavAddressbook.GetRadicaleUrl(myUri, delUser.Email.ToLower(), true, true); var addBookCollection = _cardDavAddressbook.GetCollection(requestUrlBook, userAuthorization, myUri.ToString()).Result; if (addBookCollection.Completed && addBookCollection.StatusCode != 404) { var davbookRequest = new DavRequest() { Url = requestUrlBook, Authorization = rootAuthorization, Header = myUri }; _radicaleClient.RemoveAsync(davbookRequest).ConfigureAwait(false); } foreach (var email in davUsersEmails) { var requestUrlItem = _cardDavAddressbook.GetRadicaleUrl(myUri.ToString(), email.ToLower(), true, true, itemID: delUser.Id.ToString()); try { var davItemRequest = new DavRequest() { Url = requestUrlItem, Authorization = rootAuthorization, Header = myUri }; _radicaleClient.RemoveAsync(davItemRequest).ConfigureAwait(false); } catch (Exception ex) { _log.ErrorWithException(ex); } } } catch (Exception ex) { _log.ErrorWithException(ex); } } public void SaveUserPhoto(Guid id, byte[] photo) { if (IsSystemUser(id)) { return; } _permissionContext.DemandPermissions(new UserSecurityProvider(id), Constants.Action_EditUser); _userService.SetUserPhoto(Tenant.Id, id, photo); } public byte[] GetUserPhoto(Guid id) { if (IsSystemUser(id)) { return null; } return _userService.GetUserPhoto(Tenant.Id, id); } public List GetUserGroups(Guid id) { return GetUserGroups(id, IncludeType.Distinct, Guid.Empty); } public List GetUserGroups(Guid id, Guid categoryID) { return GetUserGroups(id, IncludeType.Distinct, categoryID); } public List GetUserGroups(Guid userID, IncludeType includeType) { return GetUserGroups(userID, includeType, null); } internal List GetUserGroups(Guid userID, IncludeType includeType, Guid? categoryId) { if (_coreBaseSettings.Personal) { return new List { Constants.GroupUser, Constants.GroupEveryone }; } var httpRequestDictionary = new HttpRequestDictionary>(_accessor?.HttpContext, "GroupInfo"); var result = httpRequestDictionary.Get(userID.ToString()); if (result != null) { if (categoryId.HasValue) { result = result.Where(r => r.CategoryID.Equals(categoryId.Value)).ToList(); } return result; } result = new List(); var distinctUserGroups = new List(); var refs = GetRefsInternal(); IEnumerable userRefs = null; if (refs is UserGroupRefStore store) { userRefs = store.GetRefsByUser(userID); } var userRefsContainsNotRemoved = userRefs?.Where(r => !r.Removed && r.RefType == UserGroupRefType.Contains).ToList(); foreach (var g in GetGroupsInternal().Where(g => !categoryId.HasValue || g.CategoryID == categoryId)) { if (((g.CategoryID == Constants.SysGroupCategoryId || userRefs == null) && IsUserInGroupInternal(userID, g.ID, refs)) || (userRefsContainsNotRemoved != null && userRefsContainsNotRemoved.Any(r => r.GroupId == g.ID))) { distinctUserGroups.Add(g); } } if (IncludeType.Distinct == (includeType & IncludeType.Distinct)) { result.AddRange(distinctUserGroups); } result.Sort((group1, group2) => string.Compare(group1.Name, group2.Name, StringComparison.Ordinal)); httpRequestDictionary.Add(userID.ToString(), result); if (categoryId.HasValue) { result = result.Where(r => r.CategoryID.Equals(categoryId.Value)).ToList(); } return result; } public bool IsUserInGroup(Guid userId, Guid groupId) { return IsUserInGroupInternal(userId, groupId, GetRefsInternal()); } public UserInfo[] GetUsersByGroup(Guid groupId, EmployeeStatus employeeStatus = EmployeeStatus.Default) { var refs = GetRefsInternal(); return GetUsers(employeeStatus).Where(u => IsUserInGroupInternal(u.Id, groupId, refs)).ToArray(); } public void AddUserIntoGroup(Guid userId, Guid groupId) { if (Constants.LostUser.Id == userId || Constants.LostGroupInfo.ID == groupId) { return; } _permissionContext.DemandPermissions(Constants.Action_EditGroups); _userService.SaveUserGroupRef(Tenant.Id, new UserGroupRef(userId, groupId, UserGroupRefType.Contains)); ResetGroupCache(userId); var user = GetUsers(userId); if (groupId == Constants.GroupVisitor.ID) { var tenant = _tenantManager.GetCurrentTenant(); var myUri = (_accessor?.HttpContext != null) ? _accessor.HttpContext.Request.GetUrlRewriter().ToString() : (_cache.Get("REWRITE_URL" + tenant.Id) != null) ? new Uri(_cache.Get("REWRITE_URL" + tenant.Id)).ToString() : tenant.GetTenantDomain(_coreSettings); _cardDavAddressbook.Delete(myUri, user.Id, user.Email, tenant.Id).Wait(); //todo } } public void RemoveUserFromGroup(Guid userId, Guid groupId) { if (Constants.LostUser.Id == userId || Constants.LostGroupInfo.ID == groupId) { return; } _permissionContext.DemandPermissions(Constants.Action_EditGroups); _userService.RemoveUserGroupRef(Tenant.Id, userId, groupId, UserGroupRefType.Contains); ResetGroupCache(userId); } internal void ResetGroupCache(Guid userID) { new HttpRequestDictionary>(_accessor?.HttpContext, "GroupInfo").Reset(userID.ToString()); new HttpRequestDictionary>(_accessor?.HttpContext, "GroupInfoID").Reset(userID.ToString()); } #endregion Users #region Company public GroupInfo[] GetDepartments() { return GetGroups(); } public Guid GetDepartmentManager(Guid deparmentID) { var groupRef = _userService.GetUserGroupRef(Tenant.Id, deparmentID, UserGroupRefType.Manager); if (groupRef == null) { return Guid.Empty; } return groupRef.UserId; } public void SetDepartmentManager(Guid deparmentID, Guid userID) { var managerId = GetDepartmentManager(deparmentID); if (managerId != Guid.Empty) { _userService.RemoveUserGroupRef( Tenant.Id, managerId, deparmentID, UserGroupRefType.Manager); } if (userID != Guid.Empty) { _userService.SaveUserGroupRef( Tenant.Id, new UserGroupRef(userID, deparmentID, UserGroupRefType.Manager)); } } public UserInfo GetCompanyCEO() { var id = GetDepartmentManager(Guid.Empty); return id != Guid.Empty ? GetUsers(id) : null; } public void SetCompanyCEO(Guid userId) { SetDepartmentManager(Guid.Empty, userId); } #endregion Company #region Groups public GroupInfo[] GetGroups() { return GetGroups(Guid.Empty); } public GroupInfo[] GetGroups(Guid categoryID) { return GetGroupsInternal() .Where(g => g.CategoryID == categoryID) .ToArray(); } public GroupInfo GetGroupInfo(Guid groupID) { var group = _userService.GetGroup(Tenant.Id, groupID); if (group == null) { group = ToGroup(Constants.BuildinGroups.FirstOrDefault(r => r.ID == groupID) ?? Constants.LostGroupInfo); } if (group == null) { return Constants.LostGroupInfo; } return new GroupInfo { ID = group.Id, CategoryID = group.CategoryId, Name = group.Name, Sid = group.Sid }; } public GroupInfo GetGroupInfoBySid(string sid) { return GetGroupsInternal() .SingleOrDefault(g => g.Sid == sid) ?? Constants.LostGroupInfo; } public GroupInfo SaveGroupInfo(GroupInfo g) { if (Constants.LostGroupInfo.Equals(g)) { return Constants.LostGroupInfo; } if (Constants.BuildinGroups.Any(b => b.ID == g.ID)) { return Constants.BuildinGroups.Single(b => b.ID == g.ID); } _permissionContext.DemandPermissions(Constants.Action_EditGroups); var newGroup = _userService.SaveGroup(Tenant.Id, ToGroup(g)); return new GroupInfo(newGroup.CategoryId) { ID = newGroup.Id, Name = newGroup.Name, Sid = newGroup.Sid }; } public void DeleteGroup(Guid id) { if (Constants.LostGroupInfo.Equals(id)) { return; } if (Constants.BuildinGroups.Any(b => b.ID == id)) { return; } _permissionContext.DemandPermissions(Constants.Action_EditGroups); _userService.RemoveGroup(Tenant.Id, id); } #endregion Groups private bool IsPropertiesContainsWords(IEnumerable properties, IEnumerable words) { foreach (var w in words) { var find = false; foreach (var p in properties) { find = (2 <= w.Length) && (0 <= p.IndexOf(w, StringComparison.CurrentCultureIgnoreCase)); if (find) { break; } } if (!find) { return false; } } return true; } private IEnumerable GetUsersInternal() { return _userService.GetUsers(Tenant.Id) .Where(u => !u.Removed); } private IEnumerable GetGroupsInternal() { return _userService.GetGroups(Tenant.Id) .Where(g => !g.Removed) .Select(g => new GroupInfo(g.CategoryId) { ID = g.Id, Name = g.Name, Sid = g.Sid }) .Concat(Constants.BuildinGroups) .ToList(); } private IDictionary GetRefsInternal() { return _userService.GetUserGroupRefs(Tenant.Id); } private bool IsUserInGroupInternal(Guid userId, Guid groupId, IDictionary refs) { if (groupId == Constants.GroupEveryone.ID) { return true; } if (groupId == Constants.GroupAdmin.ID && (Tenant.OwnerId == userId || userId == Configuration.Constants.CoreSystem.ID || userId == _constants.NamingPoster.Id)) { return true; } if (groupId == Constants.GroupVisitor.ID && userId == Constants.OutsideUser.Id) { return true; } UserGroupRef r; if (groupId == Constants.GroupUser.ID || groupId == Constants.GroupVisitor.ID) { var visitor = refs.TryGetValue(UserGroupRef.CreateKey(Tenant.Id, userId, Constants.GroupVisitor.ID, UserGroupRefType.Contains), out r) && !r.Removed; if (groupId == Constants.GroupVisitor.ID) { return visitor; } if (groupId == Constants.GroupUser.ID) { return !visitor; } } return refs.TryGetValue(UserGroupRef.CreateKey(Tenant.Id, userId, groupId, UserGroupRefType.Contains), out r) && !r.Removed; } private Group ToGroup(GroupInfo g) { if (g == null) { return null; } return new Group { Id = g.ID, Name = g.Name, ParentId = g.Parent != null ? g.Parent.ID : Guid.Empty, CategoryId = g.CategoryID, Sid = g.Sid }; } }