From e4b907856c078f88038ae3120a88d4b907389125 Mon Sep 17 00:00:00 2001 From: Meik Date: Mon, 9 Mar 2026 09:11:31 +0100 Subject: [PATCH] fix: await NTFS ACL resolution for root data areas --- AGENTS.md | 67 +-- LiamNtfs/C4IT.LIAM.Ntfs.cs | 403 +++++++++-------- .../C4IT.LIAM.WorkflowactivityBase.cs | 414 +++++++++--------- 3 files changed, 452 insertions(+), 432 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d8d3a61..9aef298 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,33 +1,34 @@ -# Repository Guidelines - -## Project Structure & Module Organization - -The solution `LIAM.sln` covers all Matrix42 integration projects. Runtime code centers on `LiamM42WebApi` (service endpoints), `LiamWorkflowActivities*` (workflow logic and designers), and adapters such as `LiamActiveDirectory`, `LiamExchange`, and `LiamMsTeams`. Shared helpers live in `LiamBaseClasses` and `LiamHelper`. The `_shared` directory pins required third-party binaries; do not modify or rename them. NuGet restore artifacts belong in `packages/`, and the WinForms tooling `LiamTestTeams` supports manual verification. - -## Build, Test, and Development Commands - -Run `nuget restore LIAM.sln` once per clone to hydrate packages. Build locally with `msbuild LIAM.sln /p:Configuration=Debug`; use `Release` for deployable artifacts. For a clean rebuild, execute `msbuild LIAM.sln /t:Clean,Build /p:Configuration=Debug`. Visual Studio can open `LIAM.sln`, with `LiamM42WebApi` as the suggested startup project. When self-hosting the API, deploy it to IIS or IIS Express pointing at the project folder. - -## Coding Style & Naming Conventions - -Follow C# Allman braces with four-space indentation. Maintain `PascalCase` for classes, members, and constants (e.g., `constFragmentNameConfigProviderBase`), and `camelCase` for locals and parameters. Keep `using` directives sorted and trimmed. New projects should link `SharedAssemblyInfo.cs` to align assembly metadata. Format via Visual Studio or `dotnet format` if the SDK is available. - -## Testing Guidelines - -Automated tests are currently absent; regression work relies on targeted manual runs. Use `LiamTestTeams` to drive Microsoft Teams scenarios and validate API calls. Document manual steps in pull requests until automated coverage is added. When introducing tests, co-locate them with the feature project and add the project to `LIAM.sln` so CI can call the standard `msbuild` targets. - -## Commit & Pull Request Guidelines - -History is minimal (`initial`), so prefer concise, imperative commit subjects and reference tracking IDs when applicable (e.g., `Add Graph delta sync for users (LIAM-123)`). Squash tooling-only commits before merge. Pull requests should note the impacted integration area, configuration changes, and manual verification evidence (logs, screenshots, or request IDs). Confirm no secrets are included and request review from the owner of each touched module. - -### Mandatory Workflow Rules - -- Before starting any request that requires a code change, the repository must have no open Git changes (`git status --short` must be empty). -- If the working tree is not clean before starting a code change request, stop and clarify with the requester before proceeding. -- After completing a requested code change, always create a Git commit with a concise, fitting, imperative message. -- After completing a requested code change, always push the new commit immediately to the configured remote branch. -- Use Windows line endings (`CRLF`) for all text files in this repository. - -## Security & Configuration Tips - -Exclude environment-specific `web.config`, `app.config`, and credential artifacts from version control. Treat binaries under `_shared` as read-only dependencies. When updating external references, confirm compatibility with the target Matrix42 environment and record the expected deployment steps in the PR.*** +# Repository Guidelines + +## Project Structure & Module Organization + +The solution `LIAM.sln` covers all Matrix42 integration projects. Runtime code centers on `LiamM42WebApi` (service endpoints), `LiamWorkflowActivities*` (workflow logic and designers), and adapters such as `LiamActiveDirectory`, `LiamExchange`, and `LiamMsTeams`. Shared helpers live in `LiamBaseClasses` and `LiamHelper`. The `_shared` directory pins required third-party binaries; do not modify or rename them. NuGet restore artifacts belong in `packages/`, and the WinForms tooling `LiamTestTeams` supports manual verification. + +## Build, Test, and Development Commands + +Run `nuget restore LIAM.sln` once per clone to hydrate packages. Build locally with `msbuild LIAM.sln /p:Configuration=Debug`; use `Release` for deployable artifacts. For a clean rebuild, execute `msbuild LIAM.sln /t:Clean,Build /p:Configuration=Debug`. Visual Studio can open `LIAM.sln`, with `LiamM42WebApi` as the suggested startup project. When self-hosting the API, deploy it to IIS or IIS Express pointing at the project folder. + +## Coding Style & Naming Conventions + +Follow C# Allman braces with four-space indentation. Maintain `PascalCase` for classes, members, and constants (e.g., `constFragmentNameConfigProviderBase`), and `camelCase` for locals and parameters. Keep `using` directives sorted and trimmed. New projects should link `SharedAssemblyInfo.cs` to align assembly metadata. Format via Visual Studio or `dotnet format` if the SDK is available. + +## Testing Guidelines + +Automated tests are currently absent; regression work relies on targeted manual runs. Use `LiamTestTeams` to drive Microsoft Teams scenarios and validate API calls. Document manual steps in pull requests until automated coverage is added. When introducing tests, co-locate them with the feature project and add the project to `LIAM.sln` so CI can call the standard `msbuild` targets. + +## Commit & Pull Request Guidelines + +History is minimal (`initial`), so prefer concise, imperative commit subjects and reference tracking IDs when applicable (e.g., `Add Graph delta sync for users (LIAM-123)`). Squash tooling-only commits before merge. Pull requests should note the impacted integration area, configuration changes, and manual verification evidence (logs, screenshots, or request IDs). Confirm no secrets are included and request review from the owner of each touched module. + +### Mandatory Workflow Rules + +- Before starting any request that requires a code change, the repository must have no open Git changes (`git status --short` must be empty). +- If the working tree is not clean before starting a code change request, stop and clarify with the requester before proceeding. +- After completing a requested code change, always create a Git commit with a concise, fitting, imperative message. +- After completing a requested code change, always push the new commit immediately to the configured remote branch. +- Use Windows line endings (`CRLF`) for all text files in this repository. +- Every newly created or modified text file must be written back with Windows line endings (`CRLF`). + +## Security & Configuration Tips + +Exclude environment-specific `web.config`, `app.config`, and credential artifacts from version control. Treat binaries under `_shared` as read-only dependencies. When updating external references, confirm compatibility with the target Matrix42 environment and record the expected deployment steps in the PR.*** diff --git a/LiamNtfs/C4IT.LIAM.Ntfs.cs b/LiamNtfs/C4IT.LIAM.Ntfs.cs index 26825ae..d2858fb 100644 --- a/LiamNtfs/C4IT.LIAM.Ntfs.cs +++ b/LiamNtfs/C4IT.LIAM.Ntfs.cs @@ -1,22 +1,22 @@ -using System; -using System.Collections.Generic; -using System.DirectoryServices.AccountManagement; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Security.AccessControl; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -using C4IT.Logging; -using C4IT.Matrix42.ServerInfo; -using C4IT_IAM_Engine; -using C4IT_IAM_SET; -using LiamNtfs; -using static C4IT.Logging.cLogManager; -using static LiamNtfs.cActiveDirectoryBase; +using System; +using System.Collections.Generic; +using System.DirectoryServices.AccountManagement; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Security.AccessControl; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +using C4IT.Logging; +using C4IT.Matrix42.ServerInfo; +using C4IT_IAM_Engine; +using C4IT_IAM_SET; +using LiamNtfs; +using static C4IT.Logging.cLogManager; +using static LiamNtfs.cActiveDirectoryBase; namespace C4IT.LIAM { @@ -162,6 +162,7 @@ namespace C4IT.LIAM Path = RootPath, Level = 0 }); + await share.ResolvePermissionGroupsAsync(share.TechnicalName); DataAreas.Add(share); break; } @@ -173,6 +174,7 @@ namespace C4IT.LIAM Path = RootPath, Level = 0 }); + await NtfsRootFolder.ResolvePermissionGroupsAsync(NtfsRootFolder.TechnicalName); DataAreas.Add(NtfsRootFolder); break; } @@ -189,6 +191,7 @@ namespace C4IT.LIAM var Folder = new cLiamNtfsFolder(this, share, NtfsRootFolder, (cNtfsResultFolder)Entry.Value); + await Folder.ResolvePermissionGroupsAsync(Folder.TechnicalName); DataAreas.Add(Folder); } return DataAreas; @@ -217,6 +220,8 @@ namespace C4IT.LIAM LogEntry($"Error: License not valid", LogLevels.Error); return null; } + if (!await LogonAsync()) + return null; var splt = UID.Split(System.IO.Path.DirectorySeparatorChar); var name = Path.GetDirectoryName(UID); switch (splt.Length) @@ -226,21 +231,25 @@ namespace C4IT.LIAM return null; case 2: { - return new cLiamNtfsShare(this, new cNtfsResultShare() + var share = new cLiamNtfsShare(this, new cNtfsResultShare() { DisplayName = name, Path = UID, Level = getDepth(UID) }); + await share.ResolvePermissionGroupsAsync(share.TechnicalName); + return share; } default: { - return new cLiamNtfsFolder(this, null, null, new cNtfsResultFolder() + var folder = new cLiamNtfsFolder(this, null, null, new cNtfsResultFolder() { DisplayName = name, Path = UID, Level = getDepth(UID) }); + await folder.ResolvePermissionGroupsAsync(folder.TechnicalName); + return folder; } } } @@ -352,29 +361,195 @@ namespace C4IT.LIAM } - public override string GetLastErrorMessage() - { - var messages = new List(); - if (!string.IsNullOrEmpty(ntfsBase?.LastErrorMessage)) - messages.Add(ntfsBase.LastErrorMessage); - if (!string.IsNullOrEmpty(activeDirectoryBase?.LastErrorMessage)) - messages.Add(activeDirectoryBase.LastErrorMessage); - - return messages.Count > 0 ? string.Join(" | ", messages) : null; - } + public override string GetLastErrorMessage() + { + var messages = new List(); + if (!string.IsNullOrEmpty(ntfsBase?.LastErrorMessage)) + messages.Add(ntfsBase.LastErrorMessage); + if (!string.IsNullOrEmpty(activeDirectoryBase?.LastErrorMessage)) + messages.Add(activeDirectoryBase.LastErrorMessage); + + return messages.Count > 0 ? string.Join(" | ", messages) : null; + } } - public class cLiamNtfsShare : cLiamDataAreaBase + public abstract class cLiamNtfsPermissionDataAreaBase : cLiamDataAreaBase { public new readonly cLiamProviderNtfs Provider = null; + public string OwnerGroupIdentifier = "S-1-0-0"; + public string WriteGroupIdentifier = "S-1-0-0"; + public string ReadGroupIdentifier = "S-1-0-0"; + public string TraverseGroupIdentifier = "S-1-0-0"; + protected cLiamNtfsPermissionDataAreaBase(cLiamProviderNtfs Provider) : + base(Provider) + { + this.Provider = Provider; + this.SupportsOwners = true; + this.SupportsPermissions = true; + } + + public override async Task> GetOwnersAsync() + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + return await GetMembersAsync(true); + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + protected async Task> GetMembersAsync(bool owners) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var AD = this.Provider?.activeDirectoryBase; + if (AD == null) + { + LogEntry($"Could not get ad class from Provider for data area '{this.TechnicalName}'", LogLevels.Warning); + return null; + } + + cADCollectionBase lstMembers; + this.OwnerGroupIdentifier = this.OwnerRef ?? this.OwnerGroupIdentifier; + if (owners && !String.IsNullOrEmpty(this.OwnerGroupIdentifier)) + lstMembers = await AD.GetMembersAsync(this.OwnerGroupIdentifier); + else + lstMembers = null; + if (lstMembers == null) + { + LogEntry($"Could not get owner list for data area '{this.TechnicalName}'", LogLevels.Warning); + return null; + } + + var RetVal = new List(lstMembers.Count); + LogEntry($"Owners for data area found: {lstMembers.Count}", LogLevels.Debug); + foreach (var MemberEntry in lstMembers.Values) + { + var User = new cLiamUserInfo() + { + DisplayName = MemberEntry.DisplayName, + UserPrincipalName = (MemberEntry as cADUserResult).UserPrincipalName, + SID = MemberEntry.ID + }; + RetVal.Add(User); + } + + return RetVal; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + public async Task ResolvePermissionGroupsAsync(string path) + { + var ACLs = Provider.activeDirectoryBase.GetAccessControlList(path); + if (ACLs == null) + return; + + var ownerNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal)); + var writeNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal)); + var readNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal)); + var traverseNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Traverse); + foreach (FileSystemAccessRule rule in ACLs) + { + if (rule.IdentityReference.Value == "S-1-1-0") + continue; + + GroupPrincipal grp = GroupPrincipal.FindByIdentity(Provider.activeDirectoryBase.adContext, IdentityType.Sid, rule.IdentityReference.Value); + if (grp == null) + continue; + + DefaultLogger.LogEntry(LogLevels.Debug, $"Try matching: {grp.Name}"); + if (Regex.IsMatch(grp.SamAccountName, ownerNamingConvention.Wildcard, RegexOptions.IgnoreCase)) + { + this.OwnerGroupIdentifier = rule.IdentityReference.Value; + if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP) + { + var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName); + var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter); + var ownerNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && i.Scope == eLiamAccessRoleScopes.Global); + + foreach (var memberItem in res) + { + var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value); + if (Regex.IsMatch(SecurityGroup.TechnicalName, ownerNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase)) + this.OwnerGroupIdentifier = SecurityGroup.UID; + } + } + } + else if (Regex.IsMatch(grp.SamAccountName, writeNamingConvention.Wildcard, RegexOptions.IgnoreCase)) + { + this.WriteGroupIdentifier = rule.IdentityReference.Value; + if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP) + { + var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName); + var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter); + var writeNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && i.Scope == eLiamAccessRoleScopes.Global); + + foreach (var memberItem in res) + { + var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value); + if (Regex.IsMatch(SecurityGroup.TechnicalName, writeNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase)) + this.WriteGroupIdentifier = SecurityGroup.UID; + } + } + } + else if (Regex.IsMatch(grp.SamAccountName, readNamingConvention.Wildcard, RegexOptions.IgnoreCase)) + { + this.ReadGroupIdentifier = rule.IdentityReference.Value; + if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP) + { + var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName); + var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter); + var readNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && i.Scope == eLiamAccessRoleScopes.Global); + + foreach (var memberItem in res) + { + var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value); + if (Regex.IsMatch(SecurityGroup.TechnicalName, readNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase)) + this.ReadGroupIdentifier = SecurityGroup.UID; + } + } + } + else if (Regex.IsMatch(grp.SamAccountName, traverseNamingConvention.Wildcard, RegexOptions.IgnoreCase)) + { + this.TraverseGroupIdentifier = rule.IdentityReference.Value; + } + else + { + DefaultLogger.LogEntry(LogLevels.Debug, $"No match for: {grp.Name}"); + } + } + } + } + + public class cLiamNtfsShare : cLiamNtfsPermissionDataAreaBase + { private readonly cNtfsResultBase Share = null; public cLiamNtfsShare(cLiamProviderNtfs Provider, cNtfsResultBase Share) : base(Provider) { - this.Provider = Provider; this.Share = Share; this.DisplayName = Share.Path.Split('\\').Last(); @@ -382,8 +557,8 @@ namespace C4IT.LIAM this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(Share.Path); this.Level = Share.Level; this.DataType = eLiamDataAreaTypes.NtfsShare; - this.SupportsOwners = false; - this.SupportsPermissions = false; + if (Directory.Exists(Share.Path)) + this.CreatedDate = new DirectoryInfo(Share.Path).CreationTimeUtc.ToString("s"); } internal async Task> getFolders() @@ -447,19 +622,13 @@ namespace C4IT.LIAM this.scope = secGroup.Scope.ToString(); } } - public class cLiamNtfsFolder : cLiamDataAreaBase + public class cLiamNtfsFolder : cLiamNtfsPermissionDataAreaBase { - public new readonly cLiamProviderNtfs Provider = null; public readonly cLiamNtfsShare Share = null; public readonly cLiamNtfsFolder NtfsRootFolder = null; - public string OwnerGroupIdentifier = "S-1-0-0"; - public string WriteGroupIdentifier = "S-1-0-0"; - public string ReadGroupIdentifier = "S-1-0-0"; - public string TraverseGroupIdentifier = "S-1-0-0"; public cLiamNtfsFolder(cLiamProviderNtfs Provider, cLiamNtfsShare share, cLiamNtfsFolder ntfsRootFolder, cNtfsResultFolder NtfsFolder) : base(Provider) { var ntfsParent = NtfsFolder.Parent; - this.Provider = Provider; this.NtfsRootFolder = ntfsRootFolder; this.Share = share; this.TechnicalName = NtfsFolder.Path; @@ -467,7 +636,6 @@ namespace C4IT.LIAM this.DisplayName = new DirectoryInfo(NtfsFolder.Path).Name; this.Level = NtfsFolder.Level; this.DataType = eLiamDataAreaTypes.NtfsFolder; - this.SupportsPermissions = true; this.CreatedDate = NtfsFolder.CreatedDate; if (ntfsParent != null) { @@ -477,155 +645,6 @@ namespace C4IT.LIAM { this.ParentUID = GetUniqueDataAreaID(this.Provider.RootPath); } - assignPermissionGroups(Provider, NtfsFolder); - } - public override async Task> GetOwnersAsync() - { - var CM = MethodBase.GetCurrentMethod(); - LogMethodBegin(CM); - try - { - return await GetMembersAsync(true); - } - catch (Exception E) - { - LogException(E); - return null; - } - finally - { - LogMethodEnd(CM); - } - } - - private async Task> GetMembersAsync(bool owners) - { - var CM = MethodBase.GetCurrentMethod(); - LogMethodBegin(CM); - try - { - var AD = this.Provider?.activeDirectoryBase; - if (AD == null) - { - LogEntry($"Could not get ad class from Provider for folder '{this.TechnicalName}'", LogLevels.Warning); - return null; - } - - cADCollectionBase lstMembers; - this.OwnerGroupIdentifier = this.OwnerRef ?? this.OwnerGroupIdentifier; - if (owners && !String.IsNullOrEmpty(this.OwnerGroupIdentifier)) - lstMembers = await AD.GetMembersAsync(this.OwnerGroupIdentifier); - else - lstMembers = null; - if (lstMembers == null) - { - LogEntry($"Could not get owner list for folder '{this.TechnicalName}'", LogLevels.Warning); - return null; - } - - var RetVal = new List(lstMembers.Count); - LogEntry($"Owners for folder found: {lstMembers.Count}", LogLevels.Debug); - foreach (var MemberEntry in lstMembers.Values) - { - var User = new cLiamUserInfo() - { - DisplayName = MemberEntry.DisplayName, - UserPrincipalName = (MemberEntry as cADUserResult).UserPrincipalName, - SID = MemberEntry.ID - }; - RetVal.Add(User); - } - - return RetVal; - } - catch (Exception E) - { - LogException(E); - return null; - } - finally - { - LogMethodEnd(CM); - } - } - - private async void assignPermissionGroups(cLiamProviderNtfs Provider, cNtfsResultFolder NtfsFolder) - { - var ACLs = Provider.activeDirectoryBase.GetAccessControlList(NtfsFolder.Path); - var ownerNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal)); - var writeNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal)); - var readNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal)); - var traverseNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Traverse); - foreach (FileSystemAccessRule rule in ACLs) - { - //skip ACLs for user "everyone" - if (rule.IdentityReference.Value == "S-1-1-0") - continue; - GroupPrincipal grp = GroupPrincipal.FindByIdentity(Provider.activeDirectoryBase.adContext, IdentityType.Sid, rule.IdentityReference.Value); - - if (grp != null) - { - DefaultLogger.LogEntry(LogLevels.Debug, $"Try matching: {grp.Name}"); - if (Regex.IsMatch(grp.SamAccountName, ownerNamingConvention.Wildcard, RegexOptions.IgnoreCase)) - { - this.OwnerGroupIdentifier = rule.IdentityReference.Value; - if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP) - { - var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName); - var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter); - var ownerNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && i.Scope == eLiamAccessRoleScopes.Global); - - foreach (var memberItem in res) - { - var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value); - if (Regex.IsMatch( SecurityGroup.TechnicalName,ownerNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase)) - this.OwnerGroupIdentifier = SecurityGroup.UID; - } - } - } - else if (Regex.IsMatch(grp.SamAccountName, writeNamingConvention.Wildcard, RegexOptions.IgnoreCase)) - { - WriteGroupIdentifier = rule.IdentityReference.Value; - if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP) - { - var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName); - var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter); - var writeNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && i.Scope == eLiamAccessRoleScopes.Global); - - foreach (var memberItem in res) - { - var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value); - if (Regex.IsMatch(SecurityGroup.TechnicalName, writeNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase)) - this.WriteGroupIdentifier = SecurityGroup.UID; - } - } - } - else if (Regex.IsMatch(grp.SamAccountName, readNamingConvention.Wildcard, RegexOptions.IgnoreCase)) - { - ReadGroupIdentifier = rule.IdentityReference.Value; - if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP) - { - var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName); - var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter); - var readNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && i.Scope == eLiamAccessRoleScopes.Global); - - foreach (var memberItem in res) - { - var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value); - if (Regex.IsMatch(SecurityGroup.TechnicalName, readNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase)) - this.ReadGroupIdentifier = SecurityGroup.UID; - } - } - } - else if (Regex.IsMatch(grp.SamAccountName, traverseNamingConvention.Wildcard, RegexOptions.IgnoreCase)) - { - TraverseGroupIdentifier = rule.IdentityReference.Value; - } - else - DefaultLogger.LogEntry(LogLevels.Debug, $"No match for: {grp.Name}"); - } - - } } public static string GetUniqueDataAreaID(string fullPath) diff --git a/LiamWorkflowActivities/C4IT.LIAM.WorkflowactivityBase.cs b/LiamWorkflowActivities/C4IT.LIAM.WorkflowactivityBase.cs index 0e24d87..1660056 100644 --- a/LiamWorkflowActivities/C4IT.LIAM.WorkflowactivityBase.cs +++ b/LiamWorkflowActivities/C4IT.LIAM.WorkflowactivityBase.cs @@ -51,50 +51,50 @@ namespace LiamWorkflowActivities internal IExtensionExecutor executor; internal ISchemaReaderProvider schemaReader; - internal IDataReaderProvider dataProvider; - - public static bool IsInitialized { get; private set; } = false; - - private static Object initLock = new object(); - protected string LastOperationErrorCode { get; private set; } = string.Empty; - protected string LastOperationErrorMessage { get; private set; } = string.Empty; - - protected void ClearOperationError() - { - LastOperationErrorCode = string.Empty; - LastOperationErrorMessage = string.Empty; - } - - protected void SetOperationError(string code, string message) - { - LastOperationErrorCode = string.IsNullOrWhiteSpace(code) ? "WF_OPERATION_FAILED" : code; - LastOperationErrorMessage = message ?? string.Empty; - LogEntry($"[{LastOperationErrorCode}] {LastOperationErrorMessage}", LogLevels.Error); - } - - protected void SetOperationErrorFromProvider(cLiamProviderBase provider, string fallbackCode, string fallbackMessage) - { - if (provider is cLiamProviderExchange exProvider) - { - var code = exProvider.GetLastErrorCode(); - var message = exProvider.GetLastErrorMessage(); - if (!string.IsNullOrWhiteSpace(code) || !string.IsNullOrWhiteSpace(message)) - { - SetOperationError( - string.IsNullOrWhiteSpace(code) ? fallbackCode : code, - string.IsNullOrWhiteSpace(message) ? fallbackMessage : message); - return; - } - } - - var providerMessage = provider?.GetLastErrorMessage(); - SetOperationError( - fallbackCode, - string.IsNullOrWhiteSpace(providerMessage) ? fallbackMessage : providerMessage); - } - - protected void Initialize(NativeActivityContext context) - { + internal IDataReaderProvider dataProvider; + + public static bool IsInitialized { get; private set; } = false; + + private static Object initLock = new object(); + protected string LastOperationErrorCode { get; private set; } = string.Empty; + protected string LastOperationErrorMessage { get; private set; } = string.Empty; + + protected void ClearOperationError() + { + LastOperationErrorCode = string.Empty; + LastOperationErrorMessage = string.Empty; + } + + protected void SetOperationError(string code, string message) + { + LastOperationErrorCode = string.IsNullOrWhiteSpace(code) ? "WF_OPERATION_FAILED" : code; + LastOperationErrorMessage = message ?? string.Empty; + LogEntry($"[{LastOperationErrorCode}] {LastOperationErrorMessage}", LogLevels.Error); + } + + protected void SetOperationErrorFromProvider(cLiamProviderBase provider, string fallbackCode, string fallbackMessage) + { + if (provider is cLiamProviderExchange exProvider) + { + var code = exProvider.GetLastErrorCode(); + var message = exProvider.GetLastErrorMessage(); + if (!string.IsNullOrWhiteSpace(code) || !string.IsNullOrWhiteSpace(message)) + { + SetOperationError( + string.IsNullOrWhiteSpace(code) ? fallbackCode : code, + string.IsNullOrWhiteSpace(message) ? fallbackMessage : message); + return; + } + } + + var providerMessage = provider?.GetLastErrorMessage(); + SetOperationError( + fallbackCode, + string.IsNullOrWhiteSpace(providerMessage) ? fallbackMessage : providerMessage); + } + + protected void Initialize(NativeActivityContext context) + { try { lock (initLock) @@ -349,11 +349,11 @@ namespace LiamWorkflowActivities }); } } - var sanitizedJson = JsonConvert.SerializeObject(DataProviderData, Newtonsoft.Json.Formatting.Indented); - LogEntry("Provider configuration (sanitized JSON, copy for diagnostics tool):", LogLevels.Info); - LogEntry(sanitizedJson, LogLevels.Info); - DataProviderData.Credential.Secret = PW; - var DataProvider = CreateProviderInstance(new cLiamConfiguration(), DataProviderData); + var sanitizedJson = JsonConvert.SerializeObject(DataProviderData, Newtonsoft.Json.Formatting.Indented); + LogEntry("Provider configuration (sanitized JSON, copy for diagnostics tool):", LogLevels.Info); + LogEntry(sanitizedJson, LogLevels.Info); + DataProviderData.Credential.Secret = PW; + var DataProvider = CreateProviderInstance(new cLiamConfiguration(), DataProviderData); return DataProvider; @@ -398,45 +398,45 @@ namespace LiamWorkflowActivities } } - public async Task> getSecurityGroupsFromProvider(Guid ProviderConfigClassID) - { - var CM = MethodBase.GetCurrentMethod(); - LogMethodBegin(CM); - try - { - ClearOperationError(); - if (cC4ITLicenseM42ESM.Instance == null) - LoadLicensingInformation(); - - if (!cC4ITLicenseM42ESM.Instance.IsValid) - { - LogEntry($"Error: License not valid", LogLevels.Error); - SetOperationError("WF_GET_SECURITYGROUPS_LICENSE_INVALID", "License not valid"); - return new List(); - } - var ProviderEntry = getDataProvider(ProviderConfigClassID); - if (ProviderEntry == null) - { - LogEntry($"Could not initialize Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); - SetOperationError("WF_GET_SECURITYGROUPS_PROVIDER_NOT_FOUND", $"Could not initialize Provider config class with ID {ProviderConfigClassID}"); - return null; - } - - var lstSecurityGroups = await ProviderEntry.Provider.getSecurityGroupsAsync(ProviderEntry.Provider.GroupFilter); - if (lstSecurityGroups == null) - { - SetOperationErrorFromProvider( - ProviderEntry.Provider, - "WF_GET_SECURITYGROUPS_PROVIDER_CALL_FAILED", - "Provider returned null while reading security groups."); - return null; - } - - if (lstSecurityGroups.Count == 0) - { - LogEntry($"No security groups found for Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); - return new List(); - } + public async Task> getSecurityGroupsFromProvider(Guid ProviderConfigClassID) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + ClearOperationError(); + if (cC4ITLicenseM42ESM.Instance == null) + LoadLicensingInformation(); + + if (!cC4ITLicenseM42ESM.Instance.IsValid) + { + LogEntry($"Error: License not valid", LogLevels.Error); + SetOperationError("WF_GET_SECURITYGROUPS_LICENSE_INVALID", "License not valid"); + return new List(); + } + var ProviderEntry = getDataProvider(ProviderConfigClassID); + if (ProviderEntry == null) + { + LogEntry($"Could not initialize Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); + SetOperationError("WF_GET_SECURITYGROUPS_PROVIDER_NOT_FOUND", $"Could not initialize Provider config class with ID {ProviderConfigClassID}"); + return null; + } + + var lstSecurityGroups = await ProviderEntry.Provider.getSecurityGroupsAsync(ProviderEntry.Provider.GroupFilter); + if (lstSecurityGroups == null) + { + SetOperationErrorFromProvider( + ProviderEntry.Provider, + "WF_GET_SECURITYGROUPS_PROVIDER_CALL_FAILED", + "Provider returned null while reading security groups."); + return null; + } + + if (lstSecurityGroups.Count == 0) + { + LogEntry($"No security groups found for Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); + return new List(); + } var SGs = new List(); foreach (var sg in lstSecurityGroups) @@ -467,64 +467,64 @@ namespace LiamWorkflowActivities SGs.Add(entry); } - return SGs; - } - catch (Exception E) - { - LogException(E); - SetOperationError("WF_GET_SECURITYGROUPS_EXCEPTION", E.Message); - return null; - } - finally - { - LogMethodEnd(CM); + return SGs; + } + catch (Exception E) + { + LogException(E); + SetOperationError("WF_GET_SECURITYGROUPS_EXCEPTION", E.Message); + return null; + } + finally + { + LogMethodEnd(CM); } } - public async Task> getDataAreasFromProvider(Guid ProviderConfigClassID) - { - var CM = MethodBase.GetCurrentMethod(); - LogMethodBegin(CM); - try - { - ClearOperationError(); - if (cC4ITLicenseM42ESM.Instance == null) - LoadLicensingInformation(); - - if (!cC4ITLicenseM42ESM.Instance.IsValid) - { - LogEntry($"Error: License not valid", LogLevels.Error); - SetOperationError("WF_GET_DATAAREAS_LICENSE_INVALID", "License not valid"); - return new List(); - } - var ProviderEntry = getDataProvider(ProviderConfigClassID); - if (ProviderEntry == null) - { - LogEntry($"Could not initialize Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); - SetOperationError("WF_GET_DATAAREAS_PROVIDER_NOT_FOUND", $"Could not initialize Provider config class with ID {ProviderConfigClassID}"); - return null; - } - - var lstDataAreas = await ProviderEntry.Provider.getDataAreasAsync(ProviderEntry.Provider.MaxDepth); - if (lstDataAreas == null) - { - SetOperationErrorFromProvider( - ProviderEntry.Provider, - "WF_GET_DATAAREAS_PROVIDER_CALL_FAILED", - "Provider returned null while reading data areas."); - return null; - } - - if (lstDataAreas.Count <= 0) - { - LogEntry($"No data areas found for Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); - return new List(); - } + public async Task> getDataAreasFromProvider(Guid ProviderConfigClassID) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + ClearOperationError(); + if (cC4ITLicenseM42ESM.Instance == null) + LoadLicensingInformation(); + + if (!cC4ITLicenseM42ESM.Instance.IsValid) + { + LogEntry($"Error: License not valid", LogLevels.Error); + SetOperationError("WF_GET_DATAAREAS_LICENSE_INVALID", "License not valid"); + return new List(); + } + var ProviderEntry = getDataProvider(ProviderConfigClassID); + if (ProviderEntry == null) + { + LogEntry($"Could not initialize Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); + SetOperationError("WF_GET_DATAAREAS_PROVIDER_NOT_FOUND", $"Could not initialize Provider config class with ID {ProviderConfigClassID}"); + return null; + } + + var lstDataAreas = await ProviderEntry.Provider.getDataAreasAsync(ProviderEntry.Provider.MaxDepth); + if (lstDataAreas == null) + { + SetOperationErrorFromProvider( + ProviderEntry.Provider, + "WF_GET_DATAAREAS_PROVIDER_CALL_FAILED", + "Provider returned null while reading data areas."); + return null; + } + + if (lstDataAreas.Count <= 0) + { + LogEntry($"No data areas found for Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); + return new List(); + } return lstDataAreas .Select(DataArea => { - var ntfs = DataArea as cLiamNtfsFolder; + var ntfsPermissionArea = DataArea as cLiamNtfsPermissionDataAreaBase; var adGrp = DataArea as cLiamAdGroupAsDataArea; var exchMB = DataArea as cLiamExchangeSharedMailbox; var exchDL = DataArea as cLiamExchangeDistributionGroup; @@ -537,7 +537,7 @@ namespace LiamWorkflowActivities string owner = exchMB?.OwnerGroupIdentifier ?? exchDL?.OwnerGroupIdentifier ?? adGrp?.ManagedBySID - ?? ntfs?.OwnerGroupIdentifier + ?? ntfsPermissionArea?.OwnerGroupIdentifier ?? string.Empty; // 2) Write‑SID @@ -548,9 +548,9 @@ namespace LiamWorkflowActivities string write = exchMB != null ? exchMB.FullAccessGroupSid : exchDL != null - ? exchDL.MemberGroupSid + ? exchDL.MemberGroupSid : adGrp?.UID - ?? ntfs?.WriteGroupIdentifier + ?? ntfsPermissionArea?.WriteGroupIdentifier ?? string.Empty; // 3) Read‑SID @@ -559,14 +559,14 @@ namespace LiamWorkflowActivities // - NTFS-Folder: ReadGroupIdentifier string read = exchMB != null ? exchMB.SendAsGroupSid - : ntfs?.ReadGroupIdentifier + : ntfsPermissionArea?.ReadGroupIdentifier ?? string.Empty; - // 4) Traverse nur NTFS-Folder - string traverse = ntfs?.TraverseGroupIdentifier ?? string.Empty; + // 4) Traverse nur NTFS-Objekte + string traverse = ntfsPermissionArea?.TraverseGroupIdentifier ?? string.Empty; - // 5) CreatedDate nur NTFS-Folder - string created = ntfs?.CreatedDate ?? DateTime.MinValue.ToString("o"); + // 5) CreatedDate nur NTFS-Objekte + string created = ntfsPermissionArea?.CreatedDate ?? DateTime.MinValue.ToString("o"); // 6) Description: nur AD-Group string desc = adGrp?.Description ?? string.Empty; @@ -592,14 +592,14 @@ namespace LiamWorkflowActivities }) .ToList(); } - catch (Exception E) - { - LogException(E); - SetOperationError("WF_GET_DATAAREAS_EXCEPTION", E.Message); - return null; - } - finally - { + catch (Exception E) + { + LogException(E); + SetOperationError("WF_GET_DATAAREAS_EXCEPTION", E.Message); + return null; + } + finally + { LogMethodEnd(CM); } } @@ -970,32 +970,32 @@ namespace LiamWorkflowActivities return false; } - // Auf den konkreten Exchange-Provider casten - if (providerEntry.Provider is cLiamProviderExchange exProv) - { - // Aufruf der Methode im Provider - var result = exProv.exchangeManager.CreateDistributionGroupWithOwnershipGroups( - name, - alias, - displayName, - primarySmtpAddress, - out string errorCode, - out string errorMessage - ); - - if (result == null) - { - LogEntry( - $"createDistributionGroup failed [{errorCode}] {errorMessage}", - LogLevels.Error); - return false; - } - - LogEntry( - $"createDistributionGroup succeeded. ObjectGuid='{result.Item1}', GroupCount='{result.Item2?.Count ?? 0}'", - LogLevels.Info); - return true; - } + // Auf den konkreten Exchange-Provider casten + if (providerEntry.Provider is cLiamProviderExchange exProv) + { + // Aufruf der Methode im Provider + var result = exProv.exchangeManager.CreateDistributionGroupWithOwnershipGroups( + name, + alias, + displayName, + primarySmtpAddress, + out string errorCode, + out string errorMessage + ); + + if (result == null) + { + LogEntry( + $"createDistributionGroup failed [{errorCode}] {errorMessage}", + LogLevels.Error); + return false; + } + + LogEntry( + $"createDistributionGroup succeeded. ObjectGuid='{result.Item1}', GroupCount='{result.Item2?.Count ?? 0}'", + LogLevels.Info); + return true; + } LogEntry($"Provider is not a cLiamProviderExchange, but {providerEntry.Provider.GetType().Name}", LogLevels.Warning); return false; @@ -1043,32 +1043,32 @@ namespace LiamWorkflowActivities return false; } - // Auf den konkreten Exchange-Provider casten - if (providerEntry.Provider is cLiamProviderExchange exProv) - { - // Aufruf der Methode im Provider - var result = exProv.exchangeManager.CreateSharedMailboxWithOwnershipGroups( - name, - alias, - displayName, - primarySmtpAddress, - out string errorCode, - out string errorMessage - ); - - if (result == null) - { - LogEntry( - $"createSharedMailbox failed [{errorCode}] {errorMessage}", - LogLevels.Error); - return false; - } - - LogEntry( - $"createSharedMailbox succeeded. ObjectGuid='{result.Item1}', GroupCount='{result.Item2?.Count ?? 0}'", - LogLevels.Info); - return true; - } + // Auf den konkreten Exchange-Provider casten + if (providerEntry.Provider is cLiamProviderExchange exProv) + { + // Aufruf der Methode im Provider + var result = exProv.exchangeManager.CreateSharedMailboxWithOwnershipGroups( + name, + alias, + displayName, + primarySmtpAddress, + out string errorCode, + out string errorMessage + ); + + if (result == null) + { + LogEntry( + $"createSharedMailbox failed [{errorCode}] {errorMessage}", + LogLevels.Error); + return false; + } + + LogEntry( + $"createSharedMailbox succeeded. ObjectGuid='{result.Item1}', GroupCount='{result.Item2?.Count ?? 0}'", + LogLevels.Info); + return true; + } LogEntry($"Provider is not a cLiamProviderExchange, but {providerEntry.Provider.GetType().Name}", LogLevels.Warning); return false;