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 # Repository Guidelines
## Project Structure & Module Organization ## 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. 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 ## 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. 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 ## 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. 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 ## 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. 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 ## 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. 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 ### 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). - 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. - 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 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. - 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. - 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
## 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.***
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;
using System.Collections.Generic; using System.Collections.Generic;
using System.DirectoryServices.AccountManagement; using System.DirectoryServices.AccountManagement;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
using System.Security.AccessControl; using System.Security.AccessControl;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using C4IT.Logging; using C4IT.Logging;
using C4IT.Matrix42.ServerInfo; using C4IT.Matrix42.ServerInfo;
using C4IT_IAM_Engine; using C4IT_IAM_Engine;
using C4IT_IAM_SET; using C4IT_IAM_SET;
using LiamNtfs; using LiamNtfs;
using static C4IT.Logging.cLogManager; using static C4IT.Logging.cLogManager;
using static LiamNtfs.cActiveDirectoryBase; using static LiamNtfs.cActiveDirectoryBase;
namespace C4IT.LIAM namespace C4IT.LIAM
{ {
@@ -162,6 +162,7 @@ namespace C4IT.LIAM
Path = RootPath, Path = RootPath,
Level = 0 Level = 0
}); });
await share.ResolvePermissionGroupsAsync(share.TechnicalName);
DataAreas.Add(share); DataAreas.Add(share);
break; break;
} }
@@ -173,6 +174,7 @@ namespace C4IT.LIAM
Path = RootPath, Path = RootPath,
Level = 0 Level = 0
}); });
await NtfsRootFolder.ResolvePermissionGroupsAsync(NtfsRootFolder.TechnicalName);
DataAreas.Add(NtfsRootFolder); DataAreas.Add(NtfsRootFolder);
break; break;
} }
@@ -189,6 +191,7 @@ namespace C4IT.LIAM
var Folder = new cLiamNtfsFolder(this, share, NtfsRootFolder, (cNtfsResultFolder)Entry.Value); var Folder = new cLiamNtfsFolder(this, share, NtfsRootFolder, (cNtfsResultFolder)Entry.Value);
await Folder.ResolvePermissionGroupsAsync(Folder.TechnicalName);
DataAreas.Add(Folder); DataAreas.Add(Folder);
} }
return DataAreas; return DataAreas;
@@ -217,6 +220,8 @@ namespace C4IT.LIAM
LogEntry($"Error: License not valid", LogLevels.Error); LogEntry($"Error: License not valid", LogLevels.Error);
return null; return null;
} }
if (!await LogonAsync())
return null;
var splt = UID.Split(System.IO.Path.DirectorySeparatorChar); var splt = UID.Split(System.IO.Path.DirectorySeparatorChar);
var name = Path.GetDirectoryName(UID); var name = Path.GetDirectoryName(UID);
switch (splt.Length) switch (splt.Length)
@@ -226,21 +231,25 @@ namespace C4IT.LIAM
return null; return null;
case 2: case 2:
{ {
return new cLiamNtfsShare(this, new cNtfsResultShare() var share = new cLiamNtfsShare(this, new cNtfsResultShare()
{ {
DisplayName = name, DisplayName = name,
Path = UID, Path = UID,
Level = getDepth(UID) Level = getDepth(UID)
}); });
await share.ResolvePermissionGroupsAsync(share.TechnicalName);
return share;
} }
default: default:
{ {
return new cLiamNtfsFolder(this, null, null, new cNtfsResultFolder() var folder = new cLiamNtfsFolder(this, null, null, new cNtfsResultFolder()
{ {
DisplayName = name, DisplayName = name,
Path = UID, Path = UID,
Level = getDepth(UID) Level = getDepth(UID)
}); });
await folder.ResolvePermissionGroupsAsync(folder.TechnicalName);
return folder;
} }
} }
} }
@@ -352,29 +361,195 @@ namespace C4IT.LIAM
} }
public override string GetLastErrorMessage() public override string GetLastErrorMessage()
{ {
var messages = new List<string>(); var messages = new List<string>();
if (!string.IsNullOrEmpty(ntfsBase?.LastErrorMessage)) if (!string.IsNullOrEmpty(ntfsBase?.LastErrorMessage))
messages.Add(ntfsBase.LastErrorMessage); messages.Add(ntfsBase.LastErrorMessage);
if (!string.IsNullOrEmpty(activeDirectoryBase?.LastErrorMessage)) if (!string.IsNullOrEmpty(activeDirectoryBase?.LastErrorMessage))
messages.Add(activeDirectoryBase.LastErrorMessage); messages.Add(activeDirectoryBase.LastErrorMessage);
return messages.Count > 0 ? string.Join(" | ", messages) : null; return messages.Count > 0 ? string.Join(" | ", messages) : null;
} }
} }
public class cLiamNtfsShare : cLiamDataAreaBase public abstract class cLiamNtfsPermissionDataAreaBase : cLiamDataAreaBase
{ {
public new readonly cLiamProviderNtfs Provider = null; 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; private readonly cNtfsResultBase Share = null;
public cLiamNtfsShare(cLiamProviderNtfs Provider, cNtfsResultBase Share) : public cLiamNtfsShare(cLiamProviderNtfs Provider, cNtfsResultBase Share) :
base(Provider) base(Provider)
{ {
this.Provider = Provider;
this.Share = Share; this.Share = Share;
this.DisplayName = Share.Path.Split('\\').Last(); this.DisplayName = Share.Path.Split('\\').Last();
@@ -382,8 +557,8 @@ namespace C4IT.LIAM
this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(Share.Path); this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(Share.Path);
this.Level = Share.Level; this.Level = Share.Level;
this.DataType = eLiamDataAreaTypes.NtfsShare; this.DataType = eLiamDataAreaTypes.NtfsShare;
this.SupportsOwners = false; if (Directory.Exists(Share.Path))
this.SupportsPermissions = false; this.CreatedDate = new DirectoryInfo(Share.Path).CreationTimeUtc.ToString("s");
} }
internal async Task<List<cLiamDataAreaBase>> getFolders() internal async Task<List<cLiamDataAreaBase>> getFolders()
@@ -447,19 +622,13 @@ namespace C4IT.LIAM
this.scope = secGroup.Scope.ToString(); 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 cLiamNtfsShare Share = null;
public readonly cLiamNtfsFolder NtfsRootFolder = 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) public cLiamNtfsFolder(cLiamProviderNtfs Provider, cLiamNtfsShare share, cLiamNtfsFolder ntfsRootFolder, cNtfsResultFolder NtfsFolder) : base(Provider)
{ {
var ntfsParent = NtfsFolder.Parent; var ntfsParent = NtfsFolder.Parent;
this.Provider = Provider;
this.NtfsRootFolder = ntfsRootFolder; this.NtfsRootFolder = ntfsRootFolder;
this.Share = share; this.Share = share;
this.TechnicalName = NtfsFolder.Path; this.TechnicalName = NtfsFolder.Path;
@@ -467,7 +636,6 @@ namespace C4IT.LIAM
this.DisplayName = new DirectoryInfo(NtfsFolder.Path).Name; this.DisplayName = new DirectoryInfo(NtfsFolder.Path).Name;
this.Level = NtfsFolder.Level; this.Level = NtfsFolder.Level;
this.DataType = eLiamDataAreaTypes.NtfsFolder; this.DataType = eLiamDataAreaTypes.NtfsFolder;
this.SupportsPermissions = true;
this.CreatedDate = NtfsFolder.CreatedDate; this.CreatedDate = NtfsFolder.CreatedDate;
if (ntfsParent != null) if (ntfsParent != null)
{ {
@@ -477,155 +645,6 @@ namespace C4IT.LIAM
{ {
this.ParentUID = GetUniqueDataAreaID(this.Provider.RootPath); 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) public static string GetUniqueDataAreaID(string fullPath)

View File

@@ -51,50 +51,50 @@ namespace LiamWorkflowActivities
internal IExtensionExecutor executor; internal IExtensionExecutor executor;
internal ISchemaReaderProvider schemaReader; internal ISchemaReaderProvider schemaReader;
internal IDataReaderProvider dataProvider; internal IDataReaderProvider dataProvider;
public static bool IsInitialized { get; private set; } = false; public static bool IsInitialized { get; private set; } = false;
private static Object initLock = new object(); private static Object initLock = new object();
protected string LastOperationErrorCode { get; private set; } = string.Empty; protected string LastOperationErrorCode { get; private set; } = string.Empty;
protected string LastOperationErrorMessage { get; private set; } = string.Empty; protected string LastOperationErrorMessage { get; private set; } = string.Empty;
protected void ClearOperationError() protected void ClearOperationError()
{ {
LastOperationErrorCode = string.Empty; LastOperationErrorCode = string.Empty;
LastOperationErrorMessage = string.Empty; LastOperationErrorMessage = string.Empty;
} }
protected void SetOperationError(string code, string message) protected void SetOperationError(string code, string message)
{ {
LastOperationErrorCode = string.IsNullOrWhiteSpace(code) ? "WF_OPERATION_FAILED" : code; LastOperationErrorCode = string.IsNullOrWhiteSpace(code) ? "WF_OPERATION_FAILED" : code;
LastOperationErrorMessage = message ?? string.Empty; LastOperationErrorMessage = message ?? string.Empty;
LogEntry($"[{LastOperationErrorCode}] {LastOperationErrorMessage}", LogLevels.Error); LogEntry($"[{LastOperationErrorCode}] {LastOperationErrorMessage}", LogLevels.Error);
} }
protected void SetOperationErrorFromProvider(cLiamProviderBase provider, string fallbackCode, string fallbackMessage) protected void SetOperationErrorFromProvider(cLiamProviderBase provider, string fallbackCode, string fallbackMessage)
{ {
if (provider is cLiamProviderExchange exProvider) if (provider is cLiamProviderExchange exProvider)
{ {
var code = exProvider.GetLastErrorCode(); var code = exProvider.GetLastErrorCode();
var message = exProvider.GetLastErrorMessage(); var message = exProvider.GetLastErrorMessage();
if (!string.IsNullOrWhiteSpace(code) || !string.IsNullOrWhiteSpace(message)) if (!string.IsNullOrWhiteSpace(code) || !string.IsNullOrWhiteSpace(message))
{ {
SetOperationError( SetOperationError(
string.IsNullOrWhiteSpace(code) ? fallbackCode : code, string.IsNullOrWhiteSpace(code) ? fallbackCode : code,
string.IsNullOrWhiteSpace(message) ? fallbackMessage : message); string.IsNullOrWhiteSpace(message) ? fallbackMessage : message);
return; return;
} }
} }
var providerMessage = provider?.GetLastErrorMessage(); var providerMessage = provider?.GetLastErrorMessage();
SetOperationError( SetOperationError(
fallbackCode, fallbackCode,
string.IsNullOrWhiteSpace(providerMessage) ? fallbackMessage : providerMessage); string.IsNullOrWhiteSpace(providerMessage) ? fallbackMessage : providerMessage);
} }
protected void Initialize(NativeActivityContext context) protected void Initialize(NativeActivityContext context)
{ {
try try
{ {
lock (initLock) lock (initLock)
@@ -349,11 +349,11 @@ namespace LiamWorkflowActivities
}); });
} }
} }
var sanitizedJson = JsonConvert.SerializeObject(DataProviderData, Newtonsoft.Json.Formatting.Indented); var sanitizedJson = JsonConvert.SerializeObject(DataProviderData, Newtonsoft.Json.Formatting.Indented);
LogEntry("Provider configuration (sanitized JSON, copy for diagnostics tool):", LogLevels.Info); LogEntry("Provider configuration (sanitized JSON, copy for diagnostics tool):", LogLevels.Info);
LogEntry(sanitizedJson, LogLevels.Info); LogEntry(sanitizedJson, LogLevels.Info);
DataProviderData.Credential.Secret = PW; DataProviderData.Credential.Secret = PW;
var DataProvider = CreateProviderInstance(new cLiamConfiguration(), DataProviderData); var DataProvider = CreateProviderInstance(new cLiamConfiguration(), DataProviderData);
return DataProvider; return DataProvider;
@@ -398,45 +398,45 @@ namespace LiamWorkflowActivities
} }
} }
public async Task<IEnumerable<SecurityGroupEntry>> getSecurityGroupsFromProvider(Guid ProviderConfigClassID) public async Task<IEnumerable<SecurityGroupEntry>> getSecurityGroupsFromProvider(Guid ProviderConfigClassID)
{ {
var CM = MethodBase.GetCurrentMethod(); var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM); LogMethodBegin(CM);
try try
{ {
ClearOperationError(); ClearOperationError();
if (cC4ITLicenseM42ESM.Instance == null) if (cC4ITLicenseM42ESM.Instance == null)
LoadLicensingInformation(); LoadLicensingInformation();
if (!cC4ITLicenseM42ESM.Instance.IsValid) if (!cC4ITLicenseM42ESM.Instance.IsValid)
{ {
LogEntry($"Error: License not valid", LogLevels.Error); LogEntry($"Error: License not valid", LogLevels.Error);
SetOperationError("WF_GET_SECURITYGROUPS_LICENSE_INVALID", "License not valid"); SetOperationError("WF_GET_SECURITYGROUPS_LICENSE_INVALID", "License not valid");
return new List<SecurityGroupEntry>(); return new List<SecurityGroupEntry>();
} }
var ProviderEntry = getDataProvider(ProviderConfigClassID); var ProviderEntry = getDataProvider(ProviderConfigClassID);
if (ProviderEntry == null) if (ProviderEntry == null)
{ {
LogEntry($"Could not initialize Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); 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}"); SetOperationError("WF_GET_SECURITYGROUPS_PROVIDER_NOT_FOUND", $"Could not initialize Provider config class with ID {ProviderConfigClassID}");
return null; return null;
} }
var lstSecurityGroups = await ProviderEntry.Provider.getSecurityGroupsAsync(ProviderEntry.Provider.GroupFilter); var lstSecurityGroups = await ProviderEntry.Provider.getSecurityGroupsAsync(ProviderEntry.Provider.GroupFilter);
if (lstSecurityGroups == null) if (lstSecurityGroups == null)
{ {
SetOperationErrorFromProvider( SetOperationErrorFromProvider(
ProviderEntry.Provider, ProviderEntry.Provider,
"WF_GET_SECURITYGROUPS_PROVIDER_CALL_FAILED", "WF_GET_SECURITYGROUPS_PROVIDER_CALL_FAILED",
"Provider returned null while reading security groups."); "Provider returned null while reading security groups.");
return null; return null;
} }
if (lstSecurityGroups.Count == 0) if (lstSecurityGroups.Count == 0)
{ {
LogEntry($"No security groups found for Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); LogEntry($"No security groups found for Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning);
return new List<SecurityGroupEntry>(); return new List<SecurityGroupEntry>();
} }
var SGs = new List<SecurityGroupEntry>(); var SGs = new List<SecurityGroupEntry>();
foreach (var sg in lstSecurityGroups) foreach (var sg in lstSecurityGroups)
@@ -467,64 +467,64 @@ namespace LiamWorkflowActivities
SGs.Add(entry); SGs.Add(entry);
} }
return SGs; return SGs;
} }
catch (Exception E) catch (Exception E)
{ {
LogException(E); LogException(E);
SetOperationError("WF_GET_SECURITYGROUPS_EXCEPTION", E.Message); SetOperationError("WF_GET_SECURITYGROUPS_EXCEPTION", E.Message);
return null; return null;
} }
finally finally
{ {
LogMethodEnd(CM); LogMethodEnd(CM);
} }
} }
public async Task<IEnumerable<DataAreaEntry>> getDataAreasFromProvider(Guid ProviderConfigClassID) public async Task<IEnumerable<DataAreaEntry>> getDataAreasFromProvider(Guid ProviderConfigClassID)
{ {
var CM = MethodBase.GetCurrentMethod(); var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM); LogMethodBegin(CM);
try try
{ {
ClearOperationError(); ClearOperationError();
if (cC4ITLicenseM42ESM.Instance == null) if (cC4ITLicenseM42ESM.Instance == null)
LoadLicensingInformation(); LoadLicensingInformation();
if (!cC4ITLicenseM42ESM.Instance.IsValid) if (!cC4ITLicenseM42ESM.Instance.IsValid)
{ {
LogEntry($"Error: License not valid", LogLevels.Error); LogEntry($"Error: License not valid", LogLevels.Error);
SetOperationError("WF_GET_DATAAREAS_LICENSE_INVALID", "License not valid"); SetOperationError("WF_GET_DATAAREAS_LICENSE_INVALID", "License not valid");
return new List<DataAreaEntry>(); return new List<DataAreaEntry>();
} }
var ProviderEntry = getDataProvider(ProviderConfigClassID); var ProviderEntry = getDataProvider(ProviderConfigClassID);
if (ProviderEntry == null) if (ProviderEntry == null)
{ {
LogEntry($"Could not initialize Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); 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}"); SetOperationError("WF_GET_DATAAREAS_PROVIDER_NOT_FOUND", $"Could not initialize Provider config class with ID {ProviderConfigClassID}");
return null; return null;
} }
var lstDataAreas = await ProviderEntry.Provider.getDataAreasAsync(ProviderEntry.Provider.MaxDepth); var lstDataAreas = await ProviderEntry.Provider.getDataAreasAsync(ProviderEntry.Provider.MaxDepth);
if (lstDataAreas == null) if (lstDataAreas == null)
{ {
SetOperationErrorFromProvider( SetOperationErrorFromProvider(
ProviderEntry.Provider, ProviderEntry.Provider,
"WF_GET_DATAAREAS_PROVIDER_CALL_FAILED", "WF_GET_DATAAREAS_PROVIDER_CALL_FAILED",
"Provider returned null while reading data areas."); "Provider returned null while reading data areas.");
return null; return null;
} }
if (lstDataAreas.Count <= 0) if (lstDataAreas.Count <= 0)
{ {
LogEntry($"No data areas found for Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning); LogEntry($"No data areas found for Provider config class with ID {ProviderConfigClassID}", LogLevels.Warning);
return new List<DataAreaEntry>(); return new List<DataAreaEntry>();
} }
return lstDataAreas return lstDataAreas
.Select(DataArea => .Select(DataArea =>
{ {
var ntfs = DataArea as cLiamNtfsFolder; var ntfsPermissionArea = DataArea as cLiamNtfsPermissionDataAreaBase;
var adGrp = DataArea as cLiamAdGroupAsDataArea; var adGrp = DataArea as cLiamAdGroupAsDataArea;
var exchMB = DataArea as cLiamExchangeSharedMailbox; var exchMB = DataArea as cLiamExchangeSharedMailbox;
var exchDL = DataArea as cLiamExchangeDistributionGroup; var exchDL = DataArea as cLiamExchangeDistributionGroup;
@@ -537,7 +537,7 @@ namespace LiamWorkflowActivities
string owner = exchMB?.OwnerGroupIdentifier string owner = exchMB?.OwnerGroupIdentifier
?? exchDL?.OwnerGroupIdentifier ?? exchDL?.OwnerGroupIdentifier
?? adGrp?.ManagedBySID ?? adGrp?.ManagedBySID
?? ntfs?.OwnerGroupIdentifier ?? ntfsPermissionArea?.OwnerGroupIdentifier
?? string.Empty; ?? string.Empty;
// 2) WriteSID // 2) WriteSID
@@ -548,9 +548,9 @@ namespace LiamWorkflowActivities
string write = exchMB != null string write = exchMB != null
? exchMB.FullAccessGroupSid ? exchMB.FullAccessGroupSid
: exchDL != null : exchDL != null
? exchDL.MemberGroupSid ? exchDL.MemberGroupSid
: adGrp?.UID : adGrp?.UID
?? ntfs?.WriteGroupIdentifier ?? ntfsPermissionArea?.WriteGroupIdentifier
?? string.Empty; ?? string.Empty;
// 3) ReadSID // 3) ReadSID
@@ -559,14 +559,14 @@ namespace LiamWorkflowActivities
// - NTFS-Folder: ReadGroupIdentifier // - NTFS-Folder: ReadGroupIdentifier
string read = exchMB != null string read = exchMB != null
? exchMB.SendAsGroupSid ? exchMB.SendAsGroupSid
: ntfs?.ReadGroupIdentifier : ntfsPermissionArea?.ReadGroupIdentifier
?? string.Empty; ?? string.Empty;
// 4) Traverse nur NTFS-Folder // 4) Traverse nur NTFS-Objekte
string traverse = ntfs?.TraverseGroupIdentifier ?? string.Empty; string traverse = ntfsPermissionArea?.TraverseGroupIdentifier ?? string.Empty;
// 5) CreatedDate nur NTFS-Folder // 5) CreatedDate nur NTFS-Objekte
string created = ntfs?.CreatedDate ?? DateTime.MinValue.ToString("o"); string created = ntfsPermissionArea?.CreatedDate ?? DateTime.MinValue.ToString("o");
// 6) Description: nur AD-Group // 6) Description: nur AD-Group
string desc = adGrp?.Description ?? string.Empty; string desc = adGrp?.Description ?? string.Empty;
@@ -592,14 +592,14 @@ namespace LiamWorkflowActivities
}) })
.ToList(); .ToList();
} }
catch (Exception E) catch (Exception E)
{ {
LogException(E); LogException(E);
SetOperationError("WF_GET_DATAAREAS_EXCEPTION", E.Message); SetOperationError("WF_GET_DATAAREAS_EXCEPTION", E.Message);
return null; return null;
} }
finally finally
{ {
LogMethodEnd(CM); LogMethodEnd(CM);
} }
} }
@@ -970,32 +970,32 @@ namespace LiamWorkflowActivities
return false; return false;
} }
// Auf den konkreten Exchange-Provider casten // Auf den konkreten Exchange-Provider casten
if (providerEntry.Provider is cLiamProviderExchange exProv) if (providerEntry.Provider is cLiamProviderExchange exProv)
{ {
// Aufruf der Methode im Provider // Aufruf der Methode im Provider
var result = exProv.exchangeManager.CreateDistributionGroupWithOwnershipGroups( var result = exProv.exchangeManager.CreateDistributionGroupWithOwnershipGroups(
name, name,
alias, alias,
displayName, displayName,
primarySmtpAddress, primarySmtpAddress,
out string errorCode, out string errorCode,
out string errorMessage out string errorMessage
); );
if (result == null) if (result == null)
{ {
LogEntry( LogEntry(
$"createDistributionGroup failed [{errorCode}] {errorMessage}", $"createDistributionGroup failed [{errorCode}] {errorMessage}",
LogLevels.Error); LogLevels.Error);
return false; return false;
} }
LogEntry( LogEntry(
$"createDistributionGroup succeeded. ObjectGuid='{result.Item1}', GroupCount='{result.Item2?.Count ?? 0}'", $"createDistributionGroup succeeded. ObjectGuid='{result.Item1}', GroupCount='{result.Item2?.Count ?? 0}'",
LogLevels.Info); LogLevels.Info);
return true; return true;
} }
LogEntry($"Provider is not a cLiamProviderExchange, but {providerEntry.Provider.GetType().Name}", LogLevels.Warning); LogEntry($"Provider is not a cLiamProviderExchange, but {providerEntry.Provider.GetType().Name}", LogLevels.Warning);
return false; return false;
@@ -1043,32 +1043,32 @@ namespace LiamWorkflowActivities
return false; return false;
} }
// Auf den konkreten Exchange-Provider casten // Auf den konkreten Exchange-Provider casten
if (providerEntry.Provider is cLiamProviderExchange exProv) if (providerEntry.Provider is cLiamProviderExchange exProv)
{ {
// Aufruf der Methode im Provider // Aufruf der Methode im Provider
var result = exProv.exchangeManager.CreateSharedMailboxWithOwnershipGroups( var result = exProv.exchangeManager.CreateSharedMailboxWithOwnershipGroups(
name, name,
alias, alias,
displayName, displayName,
primarySmtpAddress, primarySmtpAddress,
out string errorCode, out string errorCode,
out string errorMessage out string errorMessage
); );
if (result == null) if (result == null)
{ {
LogEntry( LogEntry(
$"createSharedMailbox failed [{errorCode}] {errorMessage}", $"createSharedMailbox failed [{errorCode}] {errorMessage}",
LogLevels.Error); LogLevels.Error);
return false; return false;
} }
LogEntry( LogEntry(
$"createSharedMailbox succeeded. ObjectGuid='{result.Item1}', GroupCount='{result.Item2?.Count ?? 0}'", $"createSharedMailbox succeeded. ObjectGuid='{result.Item1}', GroupCount='{result.Item2?.Count ?? 0}'",
LogLevels.Info); LogLevels.Info);
return true; return true;
} }
LogEntry($"Provider is not a cLiamProviderExchange, but {providerEntry.Provider.GetType().Name}", LogLevels.Warning); LogEntry($"Provider is not a cLiamProviderExchange, but {providerEntry.Provider.GetType().Name}", LogLevels.Warning);
return false; return false;