fix: await NTFS ACL resolution for root data areas

This commit is contained in:
Meik
2026-03-09 09:11:31 +01:00
parent 9a20f3d1f0
commit e4b907856c
3 changed files with 452 additions and 432 deletions

View File

@@ -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.***

View File

@@ -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<string>();
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<string>();
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<List<cLiamUserInfo>> 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<List<cLiamUserInfo>> 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<cLiamUserInfo>(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<List<cLiamDataAreaBase>> 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<List<cLiamUserInfo>> 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<List<cLiamUserInfo>> 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<cLiamUserInfo>(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)

View File

@@ -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<IEnumerable<SecurityGroupEntry>> 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<SecurityGroupEntry>();
}
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<SecurityGroupEntry>();
}
public async Task<IEnumerable<SecurityGroupEntry>> 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<SecurityGroupEntry>();
}
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<SecurityGroupEntry>();
}
var SGs = new List<SecurityGroupEntry>();
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<IEnumerable<DataAreaEntry>> 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<DataAreaEntry>();
}
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<DataAreaEntry>();
}
public async Task<IEnumerable<DataAreaEntry>> 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<DataAreaEntry>();
}
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<DataAreaEntry>();
}
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) WriteSID
@@ -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) ReadSID
@@ -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;