/* Myrtille: A native HTML4/5 Remote Desktop Protocol client. Copyright(c) 2014-2021 Cedric Coste Copyright(c) 2018 Paul Oliver (Olive Innovations) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ using System; using System.Collections.Generic; using System.DirectoryServices; using System.DirectoryServices.AccountManagement; using System.Linq; using System.Text; using Myrtille.Helpers; using Myrtille.Services.Contracts; namespace Myrtille.Enterprise { public class ActiveDirectory : IEnterpriseAdapter { public void Initialize() { using (var db = new MyrtilleEnterpriseDBContext()) { db.Session.RemoveRange(db.Session); db.SaveChanges(); } } public EnterpriseSession Authenticate(string username, string password, string adminGroup, string domain, string netbiosDomain) { try { using (var context = new PrincipalContext(ContextType.Domain, domain, username, password)) { UserPrincipal user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username); DirectoryEntry entry = (DirectoryEntry)user.GetUnderlyingObject(); if(user.IsAccountLockedOut()) { return new EnterpriseSession { AuthenticationErrorCode = EnterpriseAuthenticationErrorCode.USER_ACCOUNT_LOCKED }; } if(user.Enabled != null && !(bool)user.Enabled) { return new EnterpriseSession { AuthenticationErrorCode = EnterpriseAuthenticationErrorCode.ACCOUNT_DISABLED }; } if(user.AccountExpirationDate != null && (DateTime)user.AccountExpirationDate <= DateTime.Now) { return new EnterpriseSession { AuthenticationErrorCode = EnterpriseAuthenticationErrorCode.ACCOUNT_EXPIRED }; } if (!user.PasswordNeverExpires )//&& !user.UserCannotChangePassword) { var expDate = (DateTime)entry.InvokeGet("PasswordExpirationDate"); // if the expiration date is not set, its default value is 1970/01/01 if (expDate <= DateTime.Now && expDate > new DateTime(1970, 1, 1)) { return new EnterpriseSession { AuthenticationErrorCode = EnterpriseAuthenticationErrorCode.PASSWORD_EXPIRED }; } } var directoryGroups = new List(); try { directoryGroups.AddRange(user.GetGroups().Select(m => m.Name).ToList()); } catch (Exception e) { //There is an issue accessing user primary ad group remotely, //Exception: Information about the domain could not be retrieved (1355). //in that case use another method which will exclude the primary domain // might need to find another way to do this! directoryGroups.AddRange(GetDirectoryGroups(entry)); } //Add user to directory group to allow restriction to host to specific username directoryGroups.Add(username); bool isAdmin = directoryGroups.Any(m => m.Equals(adminGroup, StringComparison.InvariantCultureIgnoreCase)); string sessionID = Guid.NewGuid().ToString(); string sessionKey = Guid.NewGuid().ToString("n"); using (var db = new MyrtilleEnterpriseDBContext()) { var session = db.Session.FirstOrDefault(m => m.Username == username); if (session != null) { db.Session.Remove(session); db.SaveChanges(); } session = new Session { Domain = netbiosDomain, Username = username, Password = CryptoHelper.AES_Encrypt(CryptoHelper.RDP_Encrypt(password), sessionKey), SessionID = sessionID, IsAdmin = isAdmin }; db.Session.Add(session); db.SaveChanges(); var groups = directoryGroups.Select(x => new SessionGroup { SessionID = session.ID, DirectoryGroup = x }); db.SessionGroup.AddRange(groups); db.SaveChanges(); return new EnterpriseSession { Domain = netbiosDomain, UserName = username, SessionID = sessionID, SessionKey = sessionKey, IsAdmin = isAdmin, SingleUseConnection = false }; } } } catch(DirectoryServicesCOMException e) { var formattedError = (DirectoryExceptionHelper)e; return new EnterpriseSession { AuthenticationErrorCode = formattedError.ErrorCode }; } catch(PrincipalOperationException e) { return null; } catch (Exception e) { return new EnterpriseSession { AuthenticationErrorCode = EnterpriseAuthenticationErrorCode.UNKNOWN_ERROR }; } } public void Logout(string sessionID) { using (var db = new MyrtilleEnterpriseDBContext()) { var session = db.Session.FirstOrDefault(m => m.SessionID == sessionID); if (session != null) { db.Session.Remove(session); db.SaveChanges(); } } } public long? AddHost(EnterpriseHostEdit editHost, string sessionID) { using (var db = new MyrtilleEnterpriseDBContext()) { if (!db.Session.Any(m => m.SessionID == sessionID && m.IsAdmin && m.Expire > DateTime.Now)) return null; if (db.Host.Any(m => m.HostName.Equals(editHost.HostName,StringComparison.InvariantCultureIgnoreCase))) return null; List groups = editHost.DirectoryGroups.Split(',').ToList(); var host = new Host { HostName = editHost.HostName, HostAddress = editHost.HostAddress, VMGuid = editHost.VMGuid, VMEnhancedMode = editHost.VMEnhancedMode, Protocol = editHost.Protocol, HostType = editHost.HostType, StartRemoteProgram = editHost.StartRemoteProgram, PromptForCredentials = editHost.PromptForCredentials }; db.Host.Add(host); db.SaveChanges(); var hostAccess = groups.Select(x => new HostAccessGroups { HostID = host.ID, AccessGroup = x.Trim() }); db.HostAccessGroups.AddRange(hostAccess.Where(m => m.AccessGroup != "")); db.SaveChanges(); return host.ID; } } public EnterpriseHostEdit GetHost(long hostID, string sessionID) { using (var db = new MyrtilleEnterpriseDBContext()) { if (!db.Session.Any(m => m.SessionID == sessionID && m.IsAdmin && m.Expire > DateTime.Now)) return null; var host = db.Host.FirstOrDefault(m => m.ID == hostID); if (host == null) return null; var directoryGroupList = db.HostAccessGroups .Where(m => m.HostID == hostID) .Select(m => m.AccessGroup) .ToList(); StringBuilder directoryGroups = new StringBuilder(); var isFirst = true; foreach (string group in directoryGroupList) { if (!isFirst) directoryGroups.Append(", "); isFirst = false; directoryGroups.Append(group); } return new EnterpriseHostEdit { HostID = host.ID, HostName = host.HostName, HostAddress = host.HostAddress, VMGuid = host.VMGuid, VMEnhancedMode = host.VMEnhancedMode, DirectoryGroups = directoryGroups.ToString(), Protocol = host.Protocol, HostType = host.HostType, StartRemoteProgram = host.StartRemoteProgram, PromptForCredentials = host.PromptForCredentials }; } } public bool UpdateHost(EnterpriseHostEdit editHost, string sessionID) { using (var db = new MyrtilleEnterpriseDBContext()) { if (!db.Session.Any(m => m.SessionID == sessionID && m.IsAdmin && m.Expire > DateTime.Now)) return false; if (db.Host.Any(m => m.HostName.Equals(editHost.HostName, StringComparison.InvariantCultureIgnoreCase) && m.ID != editHost.HostID)) return false; var host = db.Host.FirstOrDefault(m => m.ID == editHost.HostID); if (host == null) return false; host.HostName = editHost.HostName; host.HostAddress = editHost.HostAddress; host.VMGuid = editHost.VMGuid; host.VMEnhancedMode = editHost.VMEnhancedMode; host.Protocol = editHost.Protocol; host.StartRemoteProgram = editHost.StartRemoteProgram; host.PromptForCredentials = editHost.PromptForCredentials; var currentGroups = db.HostAccessGroups .Where(m => m.HostID == editHost.HostID) .ToList(); IEnumerable groups = editHost.DirectoryGroups.Split(',').ToList(); var hostsToDelete = currentGroups.Where(m => !groups.Any(p => p.Equals(m.AccessGroup, StringComparison.InvariantCultureIgnoreCase))); db.HostAccessGroups.RemoveRange(hostsToDelete); var hostAccess = groups .Where(m => !currentGroups.Any(p => p.AccessGroup.Equals(m,StringComparison.InvariantCultureIgnoreCase))) .Select(x => new HostAccessGroups { HostID = host.ID, AccessGroup = x.Trim() }); db.HostAccessGroups.AddRange(hostAccess.Where(m => m.AccessGroup != "")); db.SaveChanges(); return true; } } public bool DeleteHost(long hostID, string sessionID) { using (var db = new MyrtilleEnterpriseDBContext()) { if (!db.Session.Any(m => m.SessionID == sessionID && m.IsAdmin && m.Expire > DateTime.Now)) return false; var host = db.Host.FirstOrDefault(m => m.ID == hostID); if (host == null) return false; db.Host.Remove(host); db.SaveChanges(); return true; } } /// /// Get a list of active directory groups for a user /// /// /// private List GetDirectoryGroups(DirectoryEntry entry) { var directoryGroups = new List(); var groups = entry.Properties["memberOf"]; foreach (string group in groups) { var startIndex = group.IndexOf("CN="); if (startIndex < 0) continue; else startIndex += 3; var endIndex = group.IndexOf("=", startIndex); if (endIndex < 0) endIndex = group.Length - 3; else endIndex = endIndex - 6; var length = endIndex - startIndex; directoryGroups.Add(group.Substring(startIndex, endIndex)); } return directoryGroups; } public List SessionHosts(string sessionID) { using (var db = new MyrtilleEnterpriseDBContext()) { var sessionInfo = db.Session .Where(m => m.SessionID == sessionID && m.Expire > DateTime.Now) .Select(m => new { SessionID = m.SessionID, IsAdmin = m.IsAdmin }) .FirstOrDefault(); if (sessionInfo == null) return new List(); if (sessionInfo.IsAdmin) { return (from s in db.Session from h in db.Host where s.SessionID == sessionID && s.Expire > DateTime.Now select new EnterpriseHost { HostID = h.ID, HostName = h.HostName, HostAddress = h.HostAddress, VMGuid = h.VMGuid, VMEnhancedMode = h.VMEnhancedMode, HostType = h.HostType, StartRemoteProgram = h.StartRemoteProgram, PromptForCredentials = h.PromptForCredentials }) .Distinct() .OrderBy(m => m.HostName) .ToList(); } else { return (from s in db.Session join sg in db.SessionGroup on s.ID equals sg.SessionID join hag in db.HostAccessGroups on sg.DirectoryGroup equals hag.AccessGroup join h in db.Host on hag.HostID equals h.ID where s.SessionID == sessionID && s.Expire > DateTime.Now select new EnterpriseHost { HostID = h.ID, HostName = h.HostName, HostAddress = h.HostAddress, VMGuid = h.VMGuid, VMEnhancedMode = h.VMEnhancedMode, HostType = h.HostType, StartRemoteProgram = h.StartRemoteProgram, PromptForCredentials = h.PromptForCredentials }) .Distinct() .OrderBy(m => m.HostName) .ToList(); } } } public EnterpriseConnectionDetails GetSessionConnectionDetails(string sessionID, long hostID, string sessionKey) { using (var db = new MyrtilleEnterpriseDBContext()) { var sessionInfo = db.Session .Where(m => m.SessionID == sessionID && m.Expire > DateTime.Now) .Select(m => new { SessionID = m.SessionID, OneTime = m.OneTime, IsAdmin = m.IsAdmin }) .FirstOrDefault(); EnterpriseConnectionDetails result = null; if (sessionInfo != null) { if (sessionInfo.OneTime) { result = (from s in db.Session from h in db.Host where s.SessionID == sessionID && h.ID == hostID && s.Expire > DateTime.Now select new EnterpriseConnectionDetails { HostID = h.ID , HostName = h.HostName , HostAddress = h.HostAddress , VMGuid = h.VMGuid , VMEnhancedMode = h.VMEnhancedMode , HostType = h.HostType , Domain = s.Domain , Username = s.Username , Password = s.Password , Protocol = h.Protocol , StartRemoteProgram = h.StartRemoteProgram }) .FirstOrDefault(); } else { if (sessionInfo.IsAdmin) { result = (from s in db.Session from h in db.Host join sc in db.SessionHostCredentials on new { x1 = s.ID, x2 = h.ID } equals new { x1 = sc.SessionID, x2 = sc.HostID } into scl from sc in scl.DefaultIfEmpty() where s.SessionID == sessionID && h.ID == hostID && s.Expire > DateTime.Now select new EnterpriseConnectionDetails { HostID = h.ID , HostName = h.HostName , HostAddress = h.HostAddress , VMGuid = h.VMGuid , VMEnhancedMode = h.VMEnhancedMode , HostType = h.HostType , Domain = (h.PromptForCredentials ? sc.Domain : s.Domain) , Username = (h.PromptForCredentials ? sc.Username : s.Username) , Password = (h.PromptForCredentials ? sc.Password : s.Password) , Protocol = h.Protocol , StartRemoteProgram = h.StartRemoteProgram }) .FirstOrDefault(); } else { result = (from s in db.Session join sg in db.SessionGroup on s.ID equals sg.SessionID join hag in db.HostAccessGroups on sg.DirectoryGroup equals hag.AccessGroup join h in db.Host on hag.HostID equals h.ID join sc in db.SessionHostCredentials on new { x1 = s.ID, x2 = h.ID } equals new { x1 = sc.SessionID, x2 = sc.HostID } into scl from sc in scl.DefaultIfEmpty() where s.SessionID == sessionID && h.ID == hostID && s.Expire > DateTime.Now select new EnterpriseConnectionDetails { HostID = h.ID , HostName = h.HostName , HostAddress = h.HostAddress , VMGuid = h.VMGuid , VMEnhancedMode = h.VMEnhancedMode , HostType = h.HostType , Domain = (h.PromptForCredentials ? sc.Domain : s.Domain) , Username = (h.PromptForCredentials ? sc.Username : s.Username) , Password = (h.PromptForCredentials ? sc.Password : s.Password) , Protocol = h.Protocol , StartRemoteProgram = h.StartRemoteProgram }) .FirstOrDefault(); } } if (result != null) { result.Password = CryptoHelper.AES_Decrypt(result.Password, sessionKey); } // when connected from the login page, the session logout is based on expiration or user action // when connected from a one time url, the logout is done immediately if (sessionInfo.OneTime) { Logout(sessionID); } } return result; } } public string CreateUserSession(string sessionID, long hostID, string username, string password, string domain) { using (var db = new MyrtilleEnterpriseDBContext()) { if (!db.Session.Any(m => m.SessionID == sessionID && m.IsAdmin && m.Expire > DateTime.Now)) return null; if (!db.Host.Any(m => m.ID == hostID)) return null; string newSessionID = Guid.NewGuid().ToString(); string sessionKey = Guid.NewGuid().ToString("n"); var session = new Session { Domain = domain, Username = username, Password = CryptoHelper.AES_Encrypt(CryptoHelper.RDP_Encrypt(password), sessionKey), SessionID = newSessionID, IsAdmin = false, Expire = DateTime.Now.AddHours(1), OneTime = true }; db.Session.Add(session); db.SaveChanges(); return string.Format("?SI={0}&SD={1}&SK={2}",newSessionID,hostID,sessionKey); } } public bool ChangeUserPassword(string username, string oldPassword, string newPassword, string domain) { try { using (var context = new PrincipalContext(ContextType.Domain, domain, username, oldPassword)) { UserPrincipal user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username); user.ChangePassword(oldPassword, newPassword); user.Save(); } return true; }catch(Exception e) { return false; } } public bool AddSessionHostCredentials(EnterpriseHostSessionCredentials credentials) { using (var db = new MyrtilleEnterpriseDBContext()) { var session = db.Session.FirstOrDefault(m => m.SessionID == credentials.SessionID); if (session == null) return false; if (!db.Host.Any(m => m.ID == credentials.HostID)) return false; var sessionHost = db.SessionHostCredentials.FirstOrDefault(m => m.SessionID == session.ID && m.HostID == credentials.HostID); if(sessionHost != null) { db.SessionHostCredentials.Remove(sessionHost); } sessionHost = new SessionHostCredential { SessionID = session.ID, HostID = credentials.HostID, Domain = credentials.Domain, Username = credentials.Username, Password = CryptoHelper.AES_Encrypt(CryptoHelper.RDP_Encrypt(credentials.Password), credentials.SessionKey) }; db.SessionHostCredentials.Add(sessionHost); db.SaveChanges(); return true; } } } }