using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Security; using System.Text; using System.Threading.Tasks; using System.Management.Automation; using System.Management.Automation.Runspaces; using C4IT.Logging; using static C4IT.Logging.cLogManager; using C4IT.Matrix42.ServerInfo; namespace C4IT.LIAM { public static class LiamInitializer { static public cLiamProviderBase CreateInstance(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) { return new cLiamProviderExchange(LiamConfiguration, ProviderData); } } public class cLiamProviderExchange : cLiamProviderBase { public static Guid exchangeModuleId = new Guid("A1E213C3-6517-EA11-4881-000C2980FD95"); internal readonly ExchangeManager exchangeManager; private string exchangeUri; private PSCredential credential; private string organizationalUnit; private string lastErrorMessage; private bool isLoggedOn = false; public cLiamProviderExchange(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) : base(LiamConfiguration, ProviderData) { exchangeUri = ProviderData.RootPath; if (!string.IsNullOrEmpty(ProviderData.GroupPath)) organizationalUnit = ProviderData.GroupPath; else organizationalUnit = ProviderData.Domain; // Use the credential from the provider data var securePassword = new SecureString(); foreach (char c in ProviderData.Credential.Secret) { securePassword.AppendChar(c); } credential = new PSCredential(ProviderData.Credential.Identification, securePassword); // Create the Exchange manager exchangeManager = new ExchangeManager(exchangeUri, credential, organizationalUnit); } public override async Task LogonAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { if (!cC4ITLicenseM42ESM.Instance.IsValid /*|| !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(exchangeModuleId)*/) { LogEntry($"Error: License not valid or Exchange module not licensed", LogLevels.Error); lastErrorMessage = "License not valid or Exchange module not licensed"; return false; } // Test connection by getting a simple list try { var testMailboxes = exchangeManager.GetSharedMailboxes("Name -like '*'"); if (testMailboxes != null) { LogEntry($"Successfully connected to Exchange", LogLevels.Info); isLoggedOn = true; return true; } } catch (Exception ex) { LogException(ex); lastErrorMessage = $"Failed to connect to Exchange: {ex.Message}"; isLoggedOn = false; return false; } lastErrorMessage = "Unknown error connecting to Exchange"; return false; } catch (Exception E) { LogException(E); lastErrorMessage = $"Exception during Exchange logon: {E.Message}"; return false; } finally { LogMethodEnd(CM); } } public override string GetLastErrorMessage() { return lastErrorMessage; } public override async Task> getDataAreasAsync(int MaxDepth = -1) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { if (!cC4ITLicenseM42ESM.Instance.IsValid /*|| !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(exchangeModuleId)*/) { LogEntry($"Error: License not valid or Exchange module not licensed", LogLevels.Error); return new List(); } if (!isLoggedOn && !await LogonAsync()) return null; var DataAreas = new List(); // Get Shared Mailboxes try { var sharedMailboxes = exchangeManager.GetSharedMailboxes(); foreach (var mailbox in sharedMailboxes) { var displayName = mailbox.Properties["DisplayName"]?.Value?.ToString(); var alias = mailbox.Properties["Alias"]?.Value?.ToString(); var primarySmtpAddress = mailbox.Properties["PrimarySmtpAddress"]?.Value?.ToString(); // Skip if it doesn't match the regex filter (if provided) if (!string.IsNullOrEmpty(this.DataAreaRegEx) && !System.Text.RegularExpressions.Regex.Match(displayName, this.DataAreaRegEx).Success) continue; var exchangeMailbox = new cLiamExchangeSharedMailbox(this, displayName, primarySmtpAddress, alias); DataAreas.Add(exchangeMailbox); } } catch (Exception ex) { LogException(ex); } // Get Distribution Groups try { var distributionGroups = exchangeManager.GetDistributionGroups(); foreach (var group in distributionGroups) { var displayName = group.Properties["DisplayName"]?.Value?.ToString(); var alias = group.Properties["Alias"]?.Value?.ToString(); var primarySmtpAddress = group.Properties["PrimarySmtpAddress"]?.Value?.ToString(); // Skip if it doesn't match the regex filter (if provided) if (!string.IsNullOrEmpty(this.DataAreaRegEx) && !System.Text.RegularExpressions.Regex.Match(displayName, this.DataAreaRegEx).Success) continue; var exchangeGroup = new cLiamExchangeDistributionGroup(this, displayName, primarySmtpAddress, alias); DataAreas.Add(exchangeGroup); } } catch (Exception ex) { LogException(ex); } return DataAreas; } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } return null; } public override async Task LoadDataArea(string UID) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var dataType = cLIAMHelper.getUidItem(ref UID); if (string.IsNullOrEmpty(dataType)) return null; int dataTypeInt; if (!int.TryParse(dataType, out dataTypeInt)) return null; var primarySmtpAddress = cLIAMHelper.getUidItem(ref UID); if (string.IsNullOrEmpty(primarySmtpAddress)) return null; if (!isLoggedOn && !await LogonAsync()) return null; switch ((eLiamDataAreaTypes)dataTypeInt) { case eLiamDataAreaTypes.ExchangeSharedMailbox: return await cLiamExchangeSharedMailbox.Load(this, primarySmtpAddress); case eLiamDataAreaTypes.ExchangeDistributionGroup: return await cLiamExchangeDistributionGroup.Load(this, primarySmtpAddress); default: return null; } } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task> getSecurityGroupsAsync(string groupFilter) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { if (!cC4ITLicenseM42ESM.Instance.IsValid /*|| !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(exchangeModuleId)*/) { LogEntry($"Error: License not valid or Exchange module not licensed", LogLevels.Error); return new List(); } if (!isLoggedOn && !await LogonAsync()) return null; // For Exchange, we need to use the same AD security groups as from the AD provider // This is just a placeholder implementation to return some groups from the exchange environment var securityGroups = new List(); try { // Use the Exchange PowerShell to get AD groups var groups = exchangeManager.GetSecurityGroups(groupFilter); foreach (var group in groups) { var displayName = group.Properties["DisplayName"]?.Value?.ToString(); var sid = group.Properties["Sid"]?.Value?.ToString(); var dn = group.Properties["DistinguishedName"]?.Value?.ToString(); // Skip if it doesn't match the regex filter (if provided) if (!string.IsNullOrEmpty(this.GroupRegEx) && !System.Text.RegularExpressions.Regex.Match(displayName, this.GroupRegEx).Success) continue; var securityGroup = new cLiamExchangeSecurityGroup(this, displayName, sid, dn); securityGroups.Add(securityGroup); } } catch (Exception ex) { LogException(ex); } return securityGroups; } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } return null; } // Helper methods to interact with Exchange internal async Task CreateSharedMailbox(string displayName, string alias, string ownerGroupName, string writeGroupName) { try { exchangeManager.CreateSharedMailbox(displayName, alias, null); // Create the owner (FullAccess) and write (SendAs) groups if they don't exist already exchangeManager.CreateSecurityGroup(ownerGroupName, $"{displayName} Full Access"); exchangeManager.CreateSecurityGroup(writeGroupName, $"{displayName} Send As"); // Grant permissions to the groups exchangeManager.AddMailboxPermission(displayName, ownerGroupName, "FullAccess"); exchangeManager.AddSendAsPermission(displayName, writeGroupName); return true; } catch (Exception ex) { LogException(ex); lastErrorMessage = $"Error creating shared mailbox: {ex.Message}"; return false; } } internal async Task CreateDistributionGroup(string displayName, string alias, List initialMembers = null) { try { exchangeManager.CreateDistributionGroup(displayName, alias, initialMembers); return true; } catch (Exception ex) { LogException(ex); lastErrorMessage = $"Error creating distribution group: {ex.Message}"; return false; } } internal async Task AddMemberToGroup(string groupName, string member, bool isSharedMailbox) { try { if (isSharedMailbox) { exchangeManager.AddMailboxPermission(groupName, member); } else { exchangeManager.AddMemberToDistributionGroup(groupName, member); } return true; } catch (Exception ex) { LogException(ex); lastErrorMessage = $"Error adding member to group: {ex.Message}"; return false; } } internal async Task RemoveMemberFromGroup(string groupName, string member, bool isSharedMailbox) { try { if (isSharedMailbox) { exchangeManager.RemoveMailboxPermission(groupName, member); } else { exchangeManager.RemoveMemberFromDistributionGroup(groupName, member); } return true; } catch (Exception ex) { LogException(ex); lastErrorMessage = $"Error removing member from group: {ex.Message}"; return false; } } } public class cLiamExchangeSecurityGroup : cLiamDataAreaBase { public new readonly cLiamProviderExchange Provider = null; public readonly string sid = null; public readonly string dn = null; public cLiamExchangeSecurityGroup(cLiamProviderExchange Provider, string displayName, string sid, string dn) : base(Provider) { this.Provider = Provider; this.TechnicalName = displayName; this.UID = sid; this.sid = sid; this.dn = dn; } public override Task> getChildrenAsync(int Depth = -1) { // Security groups don't have children in this context return Task.FromResult(new List()); } } public class cLiamExchangeSharedMailbox : cLiamDataAreaBase { public new readonly cLiamProviderExchange Provider = null; public readonly string PrimarySmtpAddress = null; public readonly string Alias = null; public string OwnerGroupIdentifier = null; // Full Access group public string WriteGroupIdentifier = null; // Send As group public cLiamExchangeSharedMailbox(cLiamProviderExchange Provider, string displayName, string primarySmtpAddress, string alias) : base(Provider) { this.Provider = Provider; this.TechnicalName = displayName; this.DisplayName = displayName; this.PrimarySmtpAddress = primarySmtpAddress; this.Alias = alias; this.UID = getUID(primarySmtpAddress); this.Level = 0; this.DataType = eLiamDataAreaTypes.ExchangeSharedMailbox; this.SupportsOwners = true; this.SupportsPermissions = true; // Set standard naming convention for access groups // These will be resolved to actual groups later as needed this.OwnerGroupIdentifier = $"EXCH_FA_{alias}"; // Full Access group this.WriteGroupIdentifier = $"EXCH_SA_{alias}"; // Send As group } internal static string getUID(string primarySmtpAddress) { return $"{(int)eLiamDataAreaTypes.ExchangeSharedMailbox}|{primarySmtpAddress}"; } public static async Task Load(cLiamProviderExchange Provider, string primarySmtpAddress) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { // Get the mailbox details from Exchange var mailbox = Provider.exchangeManager.GetSharedMailboxByAddress(primarySmtpAddress); if (mailbox == null) return null; var displayName = mailbox.Properties["DisplayName"]?.Value?.ToString(); var alias = mailbox.Properties["Alias"]?.Value?.ToString(); return new cLiamExchangeSharedMailbox(Provider, displayName, primarySmtpAddress, alias); } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task> GetOwnersAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { // Get members of the Full Access group var members = Provider.exchangeManager.GetMailboxPermissionMembers(this.DisplayName); if (members == null) return new List(); var ownersList = new List(); foreach (var member in members) { var userInfo = new cLiamUserInfo { DisplayName = member.Properties["DisplayName"]?.Value?.ToString(), UserPrincipalName = member.Properties["UserPrincipalName"]?.Value?.ToString(), EMail = member.Properties["PrimarySmtpAddress"]?.Value?.ToString(), SID = member.Properties["Sid"]?.Value?.ToString() }; ownersList.Add(userInfo); } return ownersList; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task GrantPermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { bool success = false; switch (Role) { case eLiamAccessRoles.Owner: // Add to Full Access group success = await Provider.AddMemberToGroup(this.DisplayName, User.UserPrincipalName, true); break; case eLiamAccessRoles.Write: // Add Send As permission success = await Provider.exchangeManager.AddSendAsPermission(this.DisplayName, User.UserPrincipalName); break; default: LogEntry($"Unsupported permission role for Exchange mailbox: {Role}", LogLevels.Warning); return new cLiamPermissionResult { Valid = false }; } return new cLiamPermissionResult { Valid = success, UserReference = User.UserPrincipalName }; } catch (Exception E) { LogException(E); return new cLiamPermissionResult { Valid = false }; } finally { LogMethodEnd(CM); } } public override async Task RevokePermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { bool success = false; switch (Role) { case eLiamAccessRoles.Owner: // Remove from Full Access group success = await Provider.RemoveMemberFromGroup(this.DisplayName, User.UserPrincipalName, true); break; case eLiamAccessRoles.Write: // Remove Send As permission success = await Provider.exchangeManager.RemoveSendAsPermission(this.DisplayName, User.UserPrincipalName); break; default: LogEntry($"Unsupported permission role for Exchange mailbox: {Role}", LogLevels.Warning); return false; } return success; } catch (Exception E) { LogException(E); return false; } finally { LogMethodEnd(CM); } } public override async Task> getChildrenAsync(int Depth = -1) { // Shared mailboxes don't have children in this context return new List(); } } public class cLiamExchangeDistributionGroup : cLiamDataAreaBase { public new readonly cLiamProviderExchange Provider = null; public readonly string PrimarySmtpAddress = null; public readonly string Alias = null; public cLiamExchangeDistributionGroup(cLiamProviderExchange Provider, string displayName, string primarySmtpAddress, string alias) : base(Provider) { this.Provider = Provider; this.TechnicalName = displayName; this.DisplayName = displayName; this.PrimarySmtpAddress = primarySmtpAddress; this.Alias = alias; this.UID = getUID(primarySmtpAddress); this.Level = 0; this.DataType = eLiamDataAreaTypes.ExchangeDistributionGroup; this.SupportsOwners = true; this.SupportsPermissions = true; } internal static string getUID(string primarySmtpAddress) { return $"{(int)eLiamDataAreaTypes.ExchangeDistributionGroup}|{primarySmtpAddress}"; } public static async Task Load(cLiamProviderExchange Provider, string primarySmtpAddress) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { // Get the group details from Exchange var group = Provider.exchangeManager.GetDistributionGroupByAddress(primarySmtpAddress); if (group == null) return null; var displayName = group.Properties["DisplayName"]?.Value?.ToString(); var alias = group.Properties["Alias"]?.Value?.ToString(); return new cLiamExchangeDistributionGroup(Provider, displayName, primarySmtpAddress, alias); } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task> GetOwnersAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { // Get members of the distribution group var members = Provider.exchangeManager.GetDistributionGroupMembers(this.DisplayName); if (members == null) return new List(); var membersList = new List(); foreach (var member in members) { var userInfo = new cLiamUserInfo { DisplayName = member.Properties["DisplayName"]?.Value?.ToString(), UserPrincipalName = member.Properties["UserPrincipalName"]?.Value?.ToString(), EMail = member.Properties["PrimarySmtpAddress"]?.Value?.ToString(), SID = member.Properties["Sid"]?.Value?.ToString() }; membersList.Add(userInfo); } return membersList; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task GrantPermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { // For distribution groups, we only support adding members (which is essentially Owner role) if (Role != eLiamAccessRoles.Owner) { LogEntry($"Only Owner role is supported for distribution groups, requested: {Role}", LogLevels.Warning); return new cLiamPermissionResult { Valid = false }; } bool success = await Provider.AddMemberToGroup(this.DisplayName, User.UserPrincipalName, false); return new cLiamPermissionResult { Valid = success, UserReference = User.UserPrincipalName }; } catch (Exception E) { LogException(E); return new cLiamPermissionResult { Valid = false }; } finally { LogMethodEnd(CM); } } public override async Task RevokePermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { // For distribution groups, we only support removing members (which is essentially Owner role) if (Role != eLiamAccessRoles.Owner) { LogEntry($"Only Owner role is supported for distribution groups, requested: {Role}", LogLevels.Warning); return false; } return await Provider.RemoveMemberFromGroup(this.DisplayName, User.UserPrincipalName, false); } catch (Exception E) { LogException(E); return false; } finally { LogMethodEnd(CM); } } public override async Task> getChildrenAsync(int Depth = -1) { // Distribution groups don't have children in this context return new List(); } } }