Compare commits
13 Commits
Bruker-Dem
...
f3af7b74f0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3af7b74f0 | ||
|
|
45009dfacc | ||
|
|
cd133c67e1 | ||
|
|
17bcf9d4fb | ||
|
|
ae65f8e758 | ||
|
|
54be771569 | ||
|
|
d95a9c0ea9 | ||
|
|
8e6bbe4bec | ||
|
|
b055e29f9a | ||
|
|
804eee20fd | ||
|
|
246af92f5d | ||
|
|
ac747028f6 | ||
|
|
f9daba1bb6 |
@@ -373,6 +373,7 @@ namespace C4IT.LIAM
|
||||
public string Description { get; set; }
|
||||
public string UniqueId { get; set; }
|
||||
public string DataAreaType { get; set; }
|
||||
public int DataAreaTypeId { get; set; }
|
||||
}
|
||||
public class LiamApiVersionInfo
|
||||
{
|
||||
|
||||
@@ -1178,6 +1178,7 @@ where ";
|
||||
Level = DataArea.Level.ToString(),
|
||||
ConfigurationId = ProviderEntry.ObjectID.ToString(),
|
||||
DataAreaType = DataArea.DataType.ToString(),
|
||||
DataAreaTypeId = (int)DataArea.DataType,
|
||||
Owner = owner,
|
||||
Write = write,
|
||||
Read = DataAreaNtfsFolder?.ReadGroupIdentifier ?? string.Empty,
|
||||
|
||||
@@ -35,19 +35,75 @@ namespace C4IT.LIAM
|
||||
|
||||
private string lastErrorMessage = null;
|
||||
|
||||
private static readonly string[] RequiredGraphRoles = new[]
|
||||
private sealed class GraphPermissionRequirement
|
||||
{
|
||||
"Application.Read.All",
|
||||
"Channel.ReadBasic.All",
|
||||
"Directory.Read.All",
|
||||
"Files.ReadWrite.All",
|
||||
public string Description { get; private set; }
|
||||
|
||||
public string[] AcceptedPermissions { get; private set; }
|
||||
|
||||
public GraphPermissionRequirement(string description, params string[] acceptedPermissions)
|
||||
{
|
||||
Description = description;
|
||||
AcceptedPermissions = acceptedPermissions ?? new string[0];
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly GraphPermissionRequirement[] RequiredGraphPermissions = new[]
|
||||
{
|
||||
new GraphPermissionRequirement(
|
||||
"Team lesen",
|
||||
"Team.ReadBasic.All",
|
||||
"Group.Read.All",
|
||||
"Group.ReadWrite.All",
|
||||
"Directory.Read.All",
|
||||
"Directory.ReadWrite.All"),
|
||||
new GraphPermissionRequirement(
|
||||
"Channels lesen",
|
||||
"Channel.ReadBasic.All",
|
||||
"ChannelSettings.Read.All",
|
||||
"ChannelSettings.ReadWrite.All",
|
||||
"Group.Read.All",
|
||||
"Group.ReadWrite.All",
|
||||
"Directory.Read.All",
|
||||
"Directory.ReadWrite.All"),
|
||||
new GraphPermissionRequirement(
|
||||
"Dateien lesen und schreiben",
|
||||
"Files.ReadWrite.All",
|
||||
"Sites.ReadWrite.All",
|
||||
"Sites.FullControl.All"),
|
||||
new GraphPermissionRequirement(
|
||||
"Benutzer lesen",
|
||||
"User.Read.All",
|
||||
"User.ReadWrite.All",
|
||||
"Directory.Read.All",
|
||||
"Directory.ReadWrite.All"),
|
||||
};
|
||||
|
||||
private static readonly GraphPermissionRequirement[] CloneBaseGraphPermissions = new[]
|
||||
{
|
||||
new GraphPermissionRequirement("Teams klonen", "Team.Create"),
|
||||
};
|
||||
|
||||
private static readonly GraphPermissionRequirement[] CloneAppsGraphPermissions = new[]
|
||||
{
|
||||
new GraphPermissionRequirement("Apps mitklonen", "Application.Read.All", "Application.ReadWrite.All"),
|
||||
};
|
||||
|
||||
private static readonly GraphPermissionRequirement[] CloneSettingsGraphPermissions = new[]
|
||||
{
|
||||
new GraphPermissionRequirement("Team-Einstellungen mitklonen", "TeamSettings.Read.All", "TeamSettings.ReadWrite.All"),
|
||||
};
|
||||
|
||||
private static readonly GraphPermissionRequirement[] CloneMemberGraphPermissions = new[]
|
||||
{
|
||||
new GraphPermissionRequirement(
|
||||
"Mitglieder mitklonen",
|
||||
"GroupMember.Read.All",
|
||||
"GroupMember.ReadWrite.All",
|
||||
"Team.Create",
|
||||
"Team.ReadBasic.All",
|
||||
"TeamSettings.Read.All",
|
||||
"User.Read.All",
|
||||
"Group.Read.All",
|
||||
"Group.ReadWrite.All",
|
||||
"Directory.Read.All",
|
||||
"Directory.ReadWrite.All"),
|
||||
};
|
||||
|
||||
private void SetLastError(string message)
|
||||
@@ -182,9 +238,57 @@ namespace C4IT.LIAM
|
||||
|
||||
private bool EnsureGraphPermissions(string accessToken)
|
||||
{
|
||||
return EnsureGraphPermissions(accessToken, RequiredGraphPermissions, null);
|
||||
}
|
||||
|
||||
private bool EnsureClonePermissions(string accessToken, int partsToClone)
|
||||
{
|
||||
var requirements = new List<GraphPermissionRequirement>(CloneBaseGraphPermissions);
|
||||
var cloneParts = (CloneTeamRequest.ClonableTeamParts)partsToClone;
|
||||
|
||||
if (cloneParts.HasFlag(CloneTeamRequest.ClonableTeamParts.Apps))
|
||||
requirements.AddRange(CloneAppsGraphPermissions);
|
||||
if (cloneParts.HasFlag(CloneTeamRequest.ClonableTeamParts.Settings))
|
||||
requirements.AddRange(CloneSettingsGraphPermissions);
|
||||
if (cloneParts.HasFlag(CloneTeamRequest.ClonableTeamParts.Members))
|
||||
requirements.AddRange(CloneMemberGraphPermissions);
|
||||
|
||||
return EnsureGraphPermissions(accessToken, requirements, "Team-Klonen");
|
||||
}
|
||||
|
||||
private bool EnsureGraphPermissions(string accessToken, IEnumerable<GraphPermissionRequirement> requirements, string operationName)
|
||||
{
|
||||
if (!TryGetGrantedGraphPermissions(accessToken, out var granted, out var errorMessage))
|
||||
{
|
||||
SetLastError(errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
var missing = requirements
|
||||
.Where(requirement => requirement.AcceptedPermissions == null || !requirement.AcceptedPermissions.Any(granted.Contains))
|
||||
.Select(requirement => $"{requirement.Description} ({string.Join(" / ", requirement.AcceptedPermissions)})")
|
||||
.ToList();
|
||||
|
||||
if (missing.Count > 0)
|
||||
{
|
||||
var prefix = string.IsNullOrWhiteSpace(operationName)
|
||||
? "Fehlende Graph-Berechtigungen: "
|
||||
: $"Fehlende Graph-Berechtigungen für {operationName}: ";
|
||||
SetLastError(prefix + string.Join(", ", missing));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetGrantedGraphPermissions(string accessToken, out HashSet<string> granted, out string errorMessage)
|
||||
{
|
||||
granted = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
errorMessage = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(accessToken))
|
||||
{
|
||||
SetLastError("Kein Access Token für Berechtigungsprüfung verfügbar");
|
||||
errorMessage = "Kein Access Token für Berechtigungsprüfung verfügbar";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -193,7 +297,7 @@ namespace C4IT.LIAM
|
||||
var parts = accessToken.Split('.');
|
||||
if (parts.Length < 2)
|
||||
{
|
||||
SetLastError("Ungültiges Access Token");
|
||||
errorMessage = "Ungültiges Access Token";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -209,12 +313,10 @@ namespace C4IT.LIAM
|
||||
var payloadObj = JsonConvert.DeserializeObject<JObject>(payloadJson);
|
||||
if (payloadObj == null)
|
||||
{
|
||||
SetLastError("Token-Payload konnte nicht gelesen werden");
|
||||
errorMessage = "Token-Payload konnte nicht gelesen werden";
|
||||
return false;
|
||||
}
|
||||
|
||||
var granted = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (payloadObj.TryGetValue("roles", out var rolesToken) && rolesToken is JArray roleArray)
|
||||
{
|
||||
foreach (var role in roleArray.Values<string>())
|
||||
@@ -231,10 +333,9 @@ namespace C4IT.LIAM
|
||||
granted.Add(scope);
|
||||
}
|
||||
|
||||
var missing = RequiredGraphRoles.Where(required => !granted.Contains(required)).ToList();
|
||||
if (missing.Count > 0)
|
||||
if (!granted.Any())
|
||||
{
|
||||
SetLastError("Fehlende Graph-Berechtigungen: " + string.Join(", ", missing));
|
||||
errorMessage = "Keine Graph-Berechtigungen im Access Token gefunden";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -242,7 +343,7 @@ namespace C4IT.LIAM
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetLastError("Berechtigungsprüfung fehlgeschlagen: " + ex.Message);
|
||||
errorMessage = "Berechtigungsprüfung fehlgeschlagen: " + ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -349,6 +450,11 @@ namespace C4IT.LIAM
|
||||
|
||||
public async Task<cMsGraphResultBase> cloneTeam(string teamId, string name, string description, int visibility, int partsToClone, string additionalMembers, string additionalOwners)
|
||||
{
|
||||
if (!await LogonAsync())
|
||||
return null;
|
||||
if (!EnsureClonePermissions(MsSharepoint.Base?.AccessToken, partsToClone))
|
||||
return null;
|
||||
|
||||
var request = new CloneTeamRequest()
|
||||
{
|
||||
DisplayName = name,
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace C4IT.LIAM
|
||||
}
|
||||
|
||||
public static Guid nftsModuleId = new Guid("77e213a1-6517-ea11-4881-000c2980fd94");
|
||||
private const string AdditionalConfigurationExcludePathsKey = "NtfsExcludePaths";
|
||||
private const string AdditionalConfigurationIncludePathsKey = "NtfsIncludePaths";
|
||||
public readonly cNtfsBase ntfsBase = new cNtfsBase();
|
||||
public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase();
|
||||
private readonly Dictionary<string, HashSet<string>> publishedShareCache = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -208,6 +210,9 @@ namespace C4IT.LIAM
|
||||
if (!await LogonAsync())
|
||||
return null;
|
||||
var classification = ClassifyPath(UID);
|
||||
if (!PathsEqual(classification?.NormalizedPath, this.RootPath) && !ShouldIncludeDataArea(classification))
|
||||
return null;
|
||||
|
||||
return await BuildDataAreaAsync(classification);
|
||||
}
|
||||
catch (Exception E)
|
||||
@@ -371,14 +376,16 @@ namespace C4IT.LIAM
|
||||
foreach (var childPath in GetServerRootChildPaths(parentClassification.NormalizedPath))
|
||||
{
|
||||
var childClassification = ClassifyPath(childPath);
|
||||
if (!ShouldIncludeDataArea(childClassification.DisplayName))
|
||||
if (!ShouldTraverseDataArea(childClassification))
|
||||
continue;
|
||||
|
||||
if (ShouldIncludeDataArea(childClassification))
|
||||
{
|
||||
var childDataArea = await BuildDataAreaAsync(childClassification);
|
||||
if (childDataArea == null)
|
||||
continue;
|
||||
|
||||
if (childDataArea != null)
|
||||
children.Add(childDataArea);
|
||||
}
|
||||
|
||||
if (depth > 1)
|
||||
children.AddRange(await GetChildDataAreasAsync(childClassification, depth - 1));
|
||||
}
|
||||
@@ -393,14 +400,16 @@ namespace C4IT.LIAM
|
||||
foreach (var entry in folderEntries.Values.OfType<cNtfsResultFolder>())
|
||||
{
|
||||
var childClassification = ClassifyPath(entry.Path);
|
||||
if (!ShouldIncludeDataArea(childClassification.DisplayName))
|
||||
if (!ShouldTraverseDataArea(childClassification))
|
||||
continue;
|
||||
|
||||
if (ShouldIncludeDataArea(childClassification))
|
||||
{
|
||||
var childDataArea = await BuildDataAreaAsync(childClassification, entry);
|
||||
if (childDataArea == null)
|
||||
continue;
|
||||
|
||||
if (childDataArea != null)
|
||||
children.Add(childDataArea);
|
||||
}
|
||||
|
||||
if (depth > 1)
|
||||
children.AddRange(await GetChildDataAreasAsync(childClassification, depth - 1));
|
||||
}
|
||||
@@ -420,7 +429,55 @@ namespace C4IT.LIAM
|
||||
.Select(shareName => BuildUncPath(new[] { serverName, shareName }, 2));
|
||||
}
|
||||
|
||||
private bool ShouldIncludeDataArea(string displayName)
|
||||
private bool ShouldIncludeDataArea(cNtfsPathClassification classification)
|
||||
{
|
||||
if (classification == null)
|
||||
return false;
|
||||
|
||||
if (!MatchesDataAreaRegEx(classification.DisplayName))
|
||||
return false;
|
||||
|
||||
string matchingConfigurationKey;
|
||||
string matchingRule;
|
||||
if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
|
||||
{
|
||||
LogEntry($"Skip NTFS path '{classification.NormalizedPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'", LogLevels.Debug);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsPathWhitelisted(classification, true, out matchingConfigurationKey, out matchingRule))
|
||||
{
|
||||
LogEntry($"Skip NTFS path '{classification.NormalizedPath}' because no AdditionalConfiguration whitelist matched", LogLevels.Debug);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShouldTraverseDataArea(cNtfsPathClassification classification)
|
||||
{
|
||||
if (classification == null)
|
||||
return false;
|
||||
|
||||
string matchingConfigurationKey;
|
||||
string matchingRule;
|
||||
if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
|
||||
{
|
||||
LogEntry($"Skip NTFS subtree '{classification.NormalizedPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'", LogLevels.Debug);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HasAdditionalConfigurationValues(AdditionalConfigurationIncludePathsKey))
|
||||
return true;
|
||||
|
||||
if (IsPathWhitelisted(classification, true, out matchingConfigurationKey, out matchingRule))
|
||||
return true;
|
||||
|
||||
LogEntry($"Skip NTFS subtree '{classification.NormalizedPath}' because it is outside AdditionalConfiguration whitelist '{AdditionalConfigurationIncludePathsKey}'", LogLevels.Debug);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool MatchesDataAreaRegEx(string displayName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.DataAreaRegEx))
|
||||
return true;
|
||||
@@ -428,6 +485,197 @@ namespace C4IT.LIAM
|
||||
return Regex.Match(displayName ?? string.Empty, this.DataAreaRegEx).Success;
|
||||
}
|
||||
|
||||
private bool IsPathBlacklisted(cNtfsPathClassification classification, out string matchingConfigurationKey, out string matchingRule)
|
||||
{
|
||||
return TryMatchPathPolicy(classification, AdditionalConfigurationExcludePathsKey, false, out matchingConfigurationKey, out matchingRule);
|
||||
}
|
||||
|
||||
private bool IsPathWhitelisted(cNtfsPathClassification classification, bool allowPathAncestorMatches, out string matchingConfigurationKey, out string matchingRule)
|
||||
{
|
||||
matchingConfigurationKey = null;
|
||||
matchingRule = null;
|
||||
|
||||
if (!HasAdditionalConfigurationValues(AdditionalConfigurationIncludePathsKey))
|
||||
return true;
|
||||
|
||||
return TryMatchPathPolicy(classification, AdditionalConfigurationIncludePathsKey, allowPathAncestorMatches, out matchingConfigurationKey, out matchingRule);
|
||||
}
|
||||
|
||||
private bool TryMatchPathPolicy(cNtfsPathClassification classification, string key, bool allowPathAncestorMatches, out string matchingConfigurationKey, out string matchingRule)
|
||||
{
|
||||
matchingConfigurationKey = null;
|
||||
matchingRule = null;
|
||||
|
||||
if (classification == null || string.IsNullOrWhiteSpace(key))
|
||||
return false;
|
||||
|
||||
var patterns = GetAdditionalConfigurationValues(key).ToList();
|
||||
if (patterns.Count == 0)
|
||||
return false;
|
||||
|
||||
foreach (var pattern in patterns)
|
||||
{
|
||||
if (!MatchesPathPolicy(classification, pattern))
|
||||
continue;
|
||||
|
||||
matchingConfigurationKey = key;
|
||||
matchingRule = pattern;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!allowPathAncestorMatches)
|
||||
return false;
|
||||
|
||||
foreach (var pattern in patterns)
|
||||
{
|
||||
if (!CanPathLeadToPattern(classification, pattern))
|
||||
continue;
|
||||
|
||||
matchingConfigurationKey = key;
|
||||
matchingRule = pattern;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HasAdditionalConfigurationValues(string key)
|
||||
{
|
||||
return GetAdditionalConfigurationValues(key).Any();
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetAdditionalConfigurationValues(string key)
|
||||
{
|
||||
if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key))
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
string rawValue;
|
||||
if (!AdditionalConfiguration.TryGetValue(key, out rawValue) || string.IsNullOrWhiteSpace(rawValue))
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return rawValue
|
||||
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(i => i.Trim())
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private string GetRelativePathFromRoot(string path)
|
||||
{
|
||||
var normalizedRoot = NormalizeUncPath(this.RootPath);
|
||||
var normalizedPath = NormalizeUncPath(path);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(normalizedRoot) || string.IsNullOrWhiteSpace(normalizedPath))
|
||||
return string.Empty;
|
||||
|
||||
if (PathsEqual(normalizedRoot, normalizedPath))
|
||||
return string.Empty;
|
||||
|
||||
var rootWithSeparator = normalizedRoot + "\\";
|
||||
if (!normalizedPath.StartsWith(rootWithSeparator, StringComparison.OrdinalIgnoreCase))
|
||||
return normalizedPath;
|
||||
|
||||
return normalizedPath.Substring(rootWithSeparator.Length)
|
||||
.Trim()
|
||||
.TrimStart('\\')
|
||||
.Replace('/', '\\');
|
||||
}
|
||||
|
||||
private bool MatchesPathPolicy(cNtfsPathClassification classification, string pattern)
|
||||
{
|
||||
if (classification == null || string.IsNullOrWhiteSpace(pattern))
|
||||
return false;
|
||||
|
||||
foreach (var candidate in GetPathPolicyCandidates(classification))
|
||||
{
|
||||
if (MatchesAdditionalConfigurationPattern(candidate, pattern))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetPathPolicyCandidates(cNtfsPathClassification classification)
|
||||
{
|
||||
if (classification == null)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
var candidates = new List<string>();
|
||||
var relativePath = GetRelativePathFromRoot(classification.NormalizedPath);
|
||||
if (!string.IsNullOrWhiteSpace(relativePath))
|
||||
candidates.Add(relativePath);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(classification.NormalizedPath))
|
||||
candidates.Add(classification.NormalizedPath);
|
||||
|
||||
return candidates
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private bool MatchesAdditionalConfigurationPattern(string value, string pattern)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(pattern))
|
||||
return false;
|
||||
|
||||
var normalizedValue = value.Trim().Replace('/', '\\').Trim('\\');
|
||||
var normalizedPattern = pattern.Trim().Replace('/', '\\').Trim('\\');
|
||||
if (string.IsNullOrWhiteSpace(normalizedPattern))
|
||||
return false;
|
||||
|
||||
var regexPattern = "^" + Regex.Escape(normalizedPattern).Replace("\\*", ".*") + "$";
|
||||
return Regex.IsMatch(normalizedValue, regexPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
|
||||
private bool CanPathLeadToPattern(cNtfsPathClassification classification, string pattern)
|
||||
{
|
||||
if (classification == null || string.IsNullOrWhiteSpace(pattern))
|
||||
return false;
|
||||
|
||||
foreach (var candidate in GetPathPolicyCandidates(classification))
|
||||
{
|
||||
if (IsPathAncestorOfPattern(candidate, pattern))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsPathAncestorOfPattern(string path, string pattern)
|
||||
{
|
||||
var normalizedPath = (path ?? string.Empty).Trim().Replace('/', '\\').Trim('\\');
|
||||
var normalizedPattern = (pattern ?? string.Empty).Trim().Replace('/', '\\').Trim('\\');
|
||||
if (string.IsNullOrWhiteSpace(normalizedPath) || string.IsNullOrWhiteSpace(normalizedPattern))
|
||||
return false;
|
||||
|
||||
var pathSegments = normalizedPath.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var patternSegments = normalizedPattern.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (pathSegments.Length > patternSegments.Length)
|
||||
return false;
|
||||
|
||||
for (var segmentIndex = 0; segmentIndex < pathSegments.Length; segmentIndex++)
|
||||
{
|
||||
if (segmentIndex >= patternSegments.Length)
|
||||
return false;
|
||||
|
||||
if (!MatchesPatternSegment(pathSegments[segmentIndex], patternSegments[segmentIndex]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool MatchesPatternSegment(string valueSegment, string patternSegment)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(valueSegment) || string.IsNullOrWhiteSpace(patternSegment))
|
||||
return false;
|
||||
|
||||
var regexPattern = "^" + Regex.Escape(patternSegment).Replace("\\*", ".*") + "$";
|
||||
return Regex.IsMatch(valueSegment, regexPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
|
||||
private List<string> GetDfsObjectPrefixes(string path)
|
||||
{
|
||||
var normalizedPath = NormalizeUncPath(path);
|
||||
@@ -645,15 +893,44 @@ namespace C4IT.LIAM
|
||||
IEnumerable<string> ownerSids,
|
||||
IEnumerable<string> readerSids,
|
||||
IEnumerable<string> writerSids,
|
||||
bool allowSharePathEnsure = false,
|
||||
bool ensureTraverseGroups = false,
|
||||
bool whatIf = false)
|
||||
{
|
||||
if (!IsPermissionManagedFolderPath(folderPath))
|
||||
var classification = ClassifyPath(folderPath);
|
||||
var allowShareKinds = allowSharePathEnsure;
|
||||
if (!IsSupportedPermissionManagedPathKind(
|
||||
classification,
|
||||
allowShareKinds
|
||||
? new[] { eNtfsPathKind.Folder, eNtfsPathKind.ClassicShare, eNtfsPathKind.DfsLink }
|
||||
: new[] { eNtfsPathKind.Folder }))
|
||||
{
|
||||
return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString())
|
||||
{
|
||||
resultErrorId = 30008,
|
||||
resultMessage = $"NTFS permission ensure is only supported for folder paths. Shares, DFS namespaces and server roots are skipped: {folderPath}"
|
||||
resultMessage = allowShareKinds
|
||||
? $"NTFS permission ensure is only supported for folder and share paths. DFS namespaces and server roots are skipped: {folderPath}"
|
||||
: $"NTFS permission ensure is only supported for folder paths unless share support is explicitly enabled. Shares, DFS namespaces and server roots are skipped: {folderPath}"
|
||||
});
|
||||
}
|
||||
|
||||
string matchingConfigurationKey;
|
||||
string matchingRule;
|
||||
if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
|
||||
{
|
||||
return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString())
|
||||
{
|
||||
resultErrorId = 30008,
|
||||
resultMessage = $"NTFS permission ensure skipped for '{folderPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'."
|
||||
});
|
||||
}
|
||||
|
||||
if (!IsPathWhitelisted(classification, false, out matchingConfigurationKey, out matchingRule))
|
||||
{
|
||||
return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString())
|
||||
{
|
||||
resultErrorId = 30008,
|
||||
resultMessage = $"NTFS permission ensure skipped for '{folderPath}' because no AdditionalConfiguration whitelist matched."
|
||||
});
|
||||
}
|
||||
|
||||
@@ -667,7 +944,12 @@ namespace C4IT.LIAM
|
||||
writerSids);
|
||||
engine.WhatIf = whatIf;
|
||||
|
||||
return Task.FromResult(engine.ensureDataAreaPermissions(ensureTraverseGroups));
|
||||
var allowTraverseGroups = classification.Kind == eNtfsPathKind.Folder && ensureTraverseGroups;
|
||||
var resultToken = engine.ensureDataAreaPermissions(allowTraverseGroups);
|
||||
if (!allowTraverseGroups && ensureTraverseGroups)
|
||||
resultToken.warnings.Add($"Traverse groups are currently only ensured for folder paths. Traverse processing was skipped for '{folderPath}'.");
|
||||
|
||||
return Task.FromResult(resultToken);
|
||||
}
|
||||
|
||||
private DataArea_FileSystem CreateFilesystemEngine(
|
||||
@@ -737,9 +1019,35 @@ namespace C4IT.LIAM
|
||||
}
|
||||
|
||||
public bool IsPermissionManagedFolderPath(string path)
|
||||
{
|
||||
return IsPermissionManagedPath(path, eNtfsPathKind.Folder);
|
||||
}
|
||||
|
||||
public bool IsPermissionManagedSharePath(string path)
|
||||
{
|
||||
return IsPermissionManagedPath(path, eNtfsPathKind.ClassicShare, eNtfsPathKind.DfsLink);
|
||||
}
|
||||
|
||||
private bool IsPermissionManagedPath(string path, params eNtfsPathKind[] supportedKinds)
|
||||
{
|
||||
var classification = ClassifyPath(path);
|
||||
return classification != null && classification.Kind == eNtfsPathKind.Folder;
|
||||
if (!IsSupportedPermissionManagedPathKind(classification, supportedKinds))
|
||||
return false;
|
||||
|
||||
string matchingConfigurationKey;
|
||||
string matchingRule;
|
||||
if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule))
|
||||
return false;
|
||||
|
||||
return IsPathWhitelisted(classification, false, out matchingConfigurationKey, out matchingRule);
|
||||
}
|
||||
|
||||
private static bool IsSupportedPermissionManagedPathKind(cNtfsPathClassification classification, params eNtfsPathKind[] supportedKinds)
|
||||
{
|
||||
if (classification == null || supportedKinds == null || supportedKinds.Length == 0)
|
||||
return false;
|
||||
|
||||
return supportedKinds.Contains(classification.Kind);
|
||||
}
|
||||
|
||||
private IEnumerable<IAM_SecurityGroupTemplate> BuildSecurityGroupTemplates()
|
||||
|
||||
@@ -539,11 +539,24 @@ namespace C4IT_IAM_SET
|
||||
null,
|
||||
Helper.MaxAdGroupNameLength,
|
||||
$"Traverse fuer '{parent.FullName}'");
|
||||
var boundedTraverseDescriptionContext = Helper.GetBoundedAdGroupTemplateContext(
|
||||
traverseGroupTemplate.DescriptionTemplate,
|
||||
true,
|
||||
relativePath,
|
||||
sanitizedSegments,
|
||||
folderName,
|
||||
null,
|
||||
Helper.MaxAdGroupDescriptionLength,
|
||||
$"Traverse fuer '{parent.FullName}'",
|
||||
"AD-Gruppenbeschreibung");
|
||||
var adjustedTraverseSegments = boundedTraverseContext.SanitizedSegments ?? Array.Empty<string>();
|
||||
var adjustedTraverseRelativePath = adjustedTraverseSegments.Length > 0 ? string.Join("_", adjustedTraverseSegments) : string.Empty;
|
||||
var adjustedTraverseFolderName = boundedTraverseContext.FolderName;
|
||||
var adjustedTraverseDescriptionSegments = boundedTraverseDescriptionContext.SanitizedSegments ?? Array.Empty<string>();
|
||||
var adjustedTraverseDescriptionRelativePath = adjustedTraverseDescriptionSegments.Length > 0 ? string.Join("_", adjustedTraverseDescriptionSegments) : string.Empty;
|
||||
var adjustedTraverseDescriptionFolderName = boundedTraverseDescriptionContext.FolderName;
|
||||
var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName);
|
||||
var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName);
|
||||
var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, adjustedTraverseDescriptionRelativePath, adjustedTraverseDescriptionSegments, adjustedTraverseDescriptionFolderName);
|
||||
|
||||
string traverseRegex = null;
|
||||
try
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace C4IT_IAM_Engine
|
||||
public static class Helper
|
||||
{
|
||||
public const int MaxAdGroupNameLength = 64;
|
||||
public const int MaxAdGroupDescriptionLength = 1024;
|
||||
public const int MaxAdGroupLoopDigits = 3;
|
||||
private const int MinLeadingRelativePathSegmentLength = 3;
|
||||
private const int MinSingleLeadingRelativePathSegmentLength = 2;
|
||||
@@ -74,7 +75,8 @@ namespace C4IT_IAM_Engine
|
||||
string folderName,
|
||||
IDictionary<string, string> replacementTags,
|
||||
int maxLength,
|
||||
string logContext)
|
||||
string logContext,
|
||||
string valueLabel = "AD-Gruppenname")
|
||||
{
|
||||
var effectiveSegments = (sanitizedSegments ?? Array.Empty<string>()).Where(i => i != null).ToArray();
|
||||
var effectiveFolderName = folderName ?? string.Empty;
|
||||
@@ -132,14 +134,14 @@ namespace C4IT_IAM_Engine
|
||||
{
|
||||
cLogManager.DefaultLogger.LogEntry(
|
||||
LogLevels.Warning,
|
||||
$"AD-Gruppenname gekuerzt ({logContext}): '{result.OriginalValue}' ({GetMeasuredTemplateLength(result.OriginalValue)}) -> '{result.FinalValue}' ({GetMeasuredTemplateLength(result.FinalValue)}), Strategie: {result.Strategy}, Limit: {maxLength}.");
|
||||
$"{valueLabel} gekuerzt ({logContext}): '{result.OriginalValue}' ({GetMeasuredTemplateLength(result.OriginalValue)}) -> '{result.FinalValue}' ({GetMeasuredTemplateLength(result.FinalValue)}), Strategie: {result.Strategy}, Limit: {maxLength}.");
|
||||
}
|
||||
|
||||
if (measuredValue.Length > maxLength)
|
||||
{
|
||||
cLogManager.DefaultLogger.LogEntry(
|
||||
LogLevels.Warning,
|
||||
$"AD-Gruppenname ueberschreitet weiterhin das sichere Limit ({logContext}): '{result.FinalValue}' ({measuredValue.Length}), Limit: {maxLength}.");
|
||||
$"{valueLabel} ueberschreitet weiterhin das sichere Limit ({logContext}): '{result.FinalValue}' ({measuredValue.Length}), Limit: {maxLength}.");
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -208,20 +208,34 @@ namespace C4IT_IAM_Engine
|
||||
Helper.MaxAdGroupNameLength,
|
||||
$"{template.Type}/{template.Scope} fuer '{newFolderPath}'");
|
||||
|
||||
var adjustedSegments = boundedNameContext.SanitizedSegments ?? Array.Empty<string>();
|
||||
var adjustedRelativePath = adjustedSegments.Length > 0 ? string.Join("_", adjustedSegments) : string.Empty;
|
||||
var adjustedFolderName = boundedNameContext.FolderName;
|
||||
var boundedDescriptionContext = Helper.GetBoundedAdGroupTemplateContext(
|
||||
template.DescriptionTemplate,
|
||||
template.Type != SecurityGroupType.Traverse,
|
||||
relativePath,
|
||||
sanitizedSegments,
|
||||
folderName,
|
||||
replacementTags,
|
||||
Helper.MaxAdGroupDescriptionLength,
|
||||
$"{template.Type}/{template.Scope} fuer '{newFolderPath}'",
|
||||
"AD-Gruppenbeschreibung");
|
||||
|
||||
template.NamingTemplate = Helper.ApplyTemplatePlaceholders(template.NamingTemplate, template.Type != SecurityGroupType.Traverse, adjustedRelativePath, adjustedSegments, adjustedFolderName)
|
||||
var adjustedNameSegments = boundedNameContext.SanitizedSegments ?? Array.Empty<string>();
|
||||
var adjustedNameRelativePath = adjustedNameSegments.Length > 0 ? string.Join("_", adjustedNameSegments) : string.Empty;
|
||||
var adjustedNameFolderName = boundedNameContext.FolderName;
|
||||
var adjustedDescriptionSegments = boundedDescriptionContext.SanitizedSegments ?? Array.Empty<string>();
|
||||
var adjustedDescriptionRelativePath = adjustedDescriptionSegments.Length > 0 ? string.Join("_", adjustedDescriptionSegments) : string.Empty;
|
||||
var adjustedDescriptionFolderName = boundedDescriptionContext.FolderName;
|
||||
|
||||
template.NamingTemplate = Helper.ApplyTemplatePlaceholders(template.NamingTemplate, template.Type != SecurityGroupType.Traverse, adjustedNameRelativePath, adjustedNameSegments, adjustedNameFolderName)
|
||||
.ReplaceTags(customTags).ReplaceTags(tags)
|
||||
.ToUpper();
|
||||
|
||||
template.DescriptionTemplate = Helper.ApplyTemplatePlaceholders(template.DescriptionTemplate, template.Type != SecurityGroupType.Traverse, adjustedRelativePath, adjustedSegments, adjustedFolderName)
|
||||
template.DescriptionTemplate = Helper.ApplyTemplatePlaceholders(template.DescriptionTemplate, template.Type != SecurityGroupType.Traverse, adjustedDescriptionRelativePath, adjustedDescriptionSegments, adjustedDescriptionFolderName)
|
||||
.ReplaceTags(customTags).ReplaceTags(tags)
|
||||
.ToUpper();
|
||||
|
||||
|
||||
template.WildcardTemplate = Helper.ApplyTemplatePlaceholders(template.WildcardTemplate, template.Type != SecurityGroupType.Traverse, adjustedRelativePath, adjustedSegments, adjustedFolderName)
|
||||
template.WildcardTemplate = Helper.ApplyTemplatePlaceholders(template.WildcardTemplate, template.Type != SecurityGroupType.Traverse, adjustedNameRelativePath, adjustedNameSegments, adjustedNameFolderName)
|
||||
.ReplaceTags(customTags).ReplaceTags(tags)
|
||||
.ToUpper();
|
||||
|
||||
|
||||
@@ -909,7 +909,7 @@ namespace C4IT.LIAM.Activities
|
||||
public InArgument<Guid> ConfigID { get; set; }
|
||||
|
||||
[Category("Input")]
|
||||
[DisplayName("Folder Path")]
|
||||
[DisplayName("Path")]
|
||||
[RequiredArgument]
|
||||
public InArgument<string> FolderPath { get; set; }
|
||||
|
||||
|
||||
@@ -198,12 +198,14 @@ namespace LiamWorkflowActivities
|
||||
return result;
|
||||
}
|
||||
|
||||
var allowSharePathEnsure = IsAdditionalConfigurationEnabled(provider, "AllowManualNtfsPermissionEnsureForShares");
|
||||
var token = await ntfsProvider.EnsureMissingPermissionGroupsAsync(
|
||||
folderPath,
|
||||
customTags,
|
||||
NormalizeIdentifierList(ownerSids),
|
||||
NormalizeIdentifierList(readerSids),
|
||||
NormalizeIdentifierList(writerSids),
|
||||
allowSharePathEnsure,
|
||||
ensureTraverseGroups,
|
||||
IsWorkflowWhatIfEnabled(provider));
|
||||
if (token == null)
|
||||
@@ -417,10 +419,16 @@ namespace LiamWorkflowActivities
|
||||
if (!(provider is cLiamProviderNtfs ntfsProvider))
|
||||
return true;
|
||||
|
||||
if (!IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroups"))
|
||||
var allowFolderEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroups");
|
||||
var allowSharePathEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroupsForShares");
|
||||
if (!allowFolderEnsure && !allowSharePathEnsure)
|
||||
return true;
|
||||
|
||||
foreach (var ntfsArea in dataAreas.OfType<cLiamNtfsFolder>())
|
||||
foreach (var ntfsArea in dataAreas
|
||||
.Where(dataArea =>
|
||||
allowFolderEnsure && dataArea is cLiamNtfsFolder
|
||||
|| allowSharePathEnsure && dataArea is cLiamNtfsShare)
|
||||
.Cast<cLiamNtfsPermissionDataAreaBase>())
|
||||
{
|
||||
var folderPath = ntfsArea.TechnicalName;
|
||||
if (string.IsNullOrWhiteSpace(folderPath))
|
||||
@@ -438,6 +446,7 @@ namespace LiamWorkflowActivities
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
allowSharePathEnsure,
|
||||
false,
|
||||
simulateOnly);
|
||||
if (ensureResult == null)
|
||||
@@ -660,7 +669,8 @@ namespace LiamWorkflowActivities
|
||||
ConfigurationId = configurationId ?? string.Empty,
|
||||
BaseFolder = ntfsFolder?.Share?.TechnicalName ?? dataArea.Provider?.RootPath ?? string.Empty,
|
||||
UniqueId = dataArea.UID ?? string.Empty,
|
||||
DataAreaType = dataArea.DataType.ToString()
|
||||
DataAreaType = dataArea.DataType.ToString(),
|
||||
DataAreaTypeId = (int)dataArea.DataType
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
29
LiamWorkflowDiagnostics/SignSourceFiles.cmd
Normal file
29
LiamWorkflowDiagnostics/SignSourceFiles.cmd
Normal file
@@ -0,0 +1,29 @@
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
set "ProductName=C4IT Light Identity Access Management"
|
||||
set "SignTool=..\..\Common Code\Tools\signtool.exe"
|
||||
set "TimeStamp=http://rfc3161timestamp.globalsign.com/advanced"
|
||||
|
||||
set "FileList="
|
||||
|
||||
if exist ".\bin\Release\LiamWorkflowDiagnostics.exe" (
|
||||
set "FileList=!FileList! ".\bin\Release\LiamWorkflowDiagnostics.exe""
|
||||
)
|
||||
|
||||
for %%F in (".\bin\Release\Liam*.dll") do (
|
||||
if exist "%%~fF" (
|
||||
set "FileList=!FileList! "%%F""
|
||||
)
|
||||
)
|
||||
|
||||
if not defined FileList (
|
||||
echo No matching release binaries found to sign.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Signing all matching files at once...
|
||||
call "%SignTool%" sign /a /tr %TimeStamp% /td SHA256 /fd SHA256 /d "%ProductName%" !FileList!
|
||||
|
||||
pause
|
||||
BIN
Sonstiges/Icon1.ico
Normal file
BIN
Sonstiges/Icon1.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 KiB |
@@ -0,0 +1,307 @@
|
||||
# LIAM NTFS AdditionalConfiguration Blacklist / Whitelist - Technischer Entwurf
|
||||
|
||||
## Ziel
|
||||
|
||||
Dieses Dokument beschreibt einen kleinen technischen Entwurf, um dem NTFS-Provider ueber `AdditionalConfiguration` eine Blacklist und spaeter optional auch eine Whitelist fuer NTFS-Pfade mitzugeben.
|
||||
|
||||
Die Policy soll klassifizierungsunabhaengig arbeiten und damit fuer Shares, DFS-Links, DFS-Namespaces und Folder dieselbe Matching-Logik verwenden.
|
||||
|
||||
## Ausgangslage
|
||||
|
||||
Der NTFS-Provider verfuegt heute bereits ueber `AdditionalConfiguration`, nutzt diese im NTFS-Code aber im Wesentlichen nur fuer boolesche Feature-Flags.
|
||||
|
||||
Die Ermittlung der DataAreas erfolgt aktuell rekursiv ueber:
|
||||
|
||||
- Root-Aufbau in `getDataAreasAsync()`
|
||||
- Kind-Ermittlung in `GetChildDataAreasAsync()`
|
||||
- Dateisystem-Enumeration je Ebene ueber `ntfsBase.RequestFoldersListAsync(parentPath, 1)`
|
||||
|
||||
Der bestehende Filter `ShouldIncludeDataArea()` wirkt nur auf den `DisplayName` und basiert auf `DataAreaRegEx`. Das ist fuer gezielte Ordnerausschluesse fachlich und technisch zu grob.
|
||||
|
||||
## Zielbild
|
||||
|
||||
Die neue Logik soll eine explizite Pfad-Policy fuer den NTFS-Provider einfuehren.
|
||||
|
||||
Diese Policy entscheidet fuer jeden gefundenen Pfad:
|
||||
|
||||
- darf als DataArea materialisiert werden
|
||||
- darf weiter traversiert werden
|
||||
|
||||
Ein ausgeschlossener Pfad soll weder als DataArea geliefert noch weiter traversiert werden.
|
||||
|
||||
## Vorgeschlagene Konfigurationsschluessel
|
||||
|
||||
### Aktueller Stand
|
||||
|
||||
- `NtfsExcludePaths`
|
||||
- `NtfsIncludePaths`
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
NtfsExcludePaths=Archiv;*\Temp;Abteilung\Alt;\\server\share\legacy\*
|
||||
NtfsIncludePaths=Fachbereiche\*;Shares\Produktion\*;\\server\dfs\namespace\link\*
|
||||
```
|
||||
|
||||
## Aktueller Implementierungsstand
|
||||
|
||||
Die nachfolgend beschriebene Path-Policy ist im NTFS-Provider inzwischen implementiert.
|
||||
|
||||
### 1. Materialisierung und Traversierung sind getrennt
|
||||
|
||||
In `GetChildDataAreasAsync()` wird pro gefundenem Pfad heute getrennt entschieden:
|
||||
|
||||
- darf der Pfad als DataArea geliefert werden
|
||||
- darf unterhalb des Pfads weiter traversiert werden
|
||||
|
||||
Das ist wichtig fuer Include-Regeln. Ein Zwischenpfad darf fuer die Traversierung erlaubt sein, auch wenn erst ein tiefer liegendes Zielobjekt tatsaechlich auf der Whitelist steht.
|
||||
|
||||
### 2. Matching ist klassifizierungsunabhaengig
|
||||
|
||||
Die Path-Policy arbeitet fuer:
|
||||
|
||||
- `ServerRoot`
|
||||
- `ClassicShare`
|
||||
- `DfsNamespaceRoot`
|
||||
- `DfsLink`
|
||||
- `Folder`
|
||||
|
||||
mit derselben Matching-Logik.
|
||||
|
||||
Die Klassifikation bleibt fuer die fachliche Verarbeitung der DataArea relevant, nicht mehr fuer Blacklist-/Whitelist-Matching.
|
||||
|
||||
### 3. Relative und absolute Pfade werden parallel ausgewertet
|
||||
|
||||
Jeder klassifizierte Pfad wird gegen mehrere Kandidaten gematcht:
|
||||
|
||||
- relativer Pfad unterhalb von `RootPath`
|
||||
- normalisierter absoluter UNC-Pfad
|
||||
|
||||
Dadurch koennen Regeln sowohl knapp relativ als auch explizit absolut formuliert werden.
|
||||
|
||||
### 4. Include-Regeln duerfen Traversierungs-Vorpfade freischalten
|
||||
|
||||
Wenn `NtfsIncludePaths` gesetzt ist, darf ein Pfad auch dann traversiert werden, wenn er selbst noch nicht final matcht, aber zu einem spaeter passenden Zielpfad fuehren kann.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
NtfsIncludePaths=Abteilung\IT\*
|
||||
```
|
||||
|
||||
Dann darf `Abteilung` fuer die Traversierung erhalten bleiben, damit `Abteilung\IT\TeamA` ueberhaupt erreicht werden kann.
|
||||
|
||||
### 5. `LoadDataArea()` respektiert die Policy
|
||||
|
||||
Direktes Laden eines Pfads ueber `LoadDataArea()` wird ebenfalls durch die Path-Policy eingeschraenkt.
|
||||
|
||||
Ausnahme:
|
||||
|
||||
- der konfigurierte `RootPath` selbst bleibt ladbar
|
||||
|
||||
Damit kann die Filterung nicht einfach durch direktes Laden einer UID umgangen werden.
|
||||
|
||||
### 6. Permission-Management bleibt fachlich auf Folder beschraenkt
|
||||
|
||||
`IsPermissionManagedFolderPath()` verwendet dieselbe generische Path-Policy, bleibt aber weiterhin nur fuer als `Folder` klassifizierte Pfade zulaessig.
|
||||
|
||||
Die Path-Policy ist also klassifizierungsunabhaengig, das Berechtigungs-Handling selbst aber bewusst nicht.
|
||||
|
||||
## Matching-Regeln
|
||||
|
||||
Empfohlene Semantik:
|
||||
|
||||
- Trennzeichen fuer Mehrfachwerte: `;`
|
||||
- Auswertung case-insensitive
|
||||
- Leerzeichen an Eintraegen vor dem Match trimmen
|
||||
- Matching gegen relative Pfade unter `RootPath` und gegen normalisierte absolute UNC-Pfade
|
||||
- Interne Normalisierung auf konsistente UNC-/Directory-Notation
|
||||
- Zunaechst nur einfache Wildcards `*` unterstuetzen, keine freien Regex-Ausdruecke
|
||||
- Include-Regeln duerfen fuer die Traversierung auch uebergeordnete Pfade freischalten, wenn diese zu einem spaeter passenden Zielpfad fuehren
|
||||
|
||||
Empfohlene Prioritaet:
|
||||
|
||||
1. Wenn keine Include-Regel gesetzt ist, sind alle Pfade grundsaetzlich erlaubt.
|
||||
2. Wenn Include-Regeln gesetzt sind, sind nur passende Pfade erlaubt.
|
||||
3. Exclude-Regeln werden danach angewendet und gewinnen bei Kollision.
|
||||
|
||||
Das entspricht dem aktuell implementierten Verhalten.
|
||||
|
||||
## Beispiele
|
||||
|
||||
### 1. Einzelnen Teilbaum ausschliessen
|
||||
|
||||
Konfiguration:
|
||||
|
||||
```text
|
||||
NtfsExcludePaths=Abteilung\Alt\*
|
||||
```
|
||||
|
||||
Wirkung:
|
||||
|
||||
- `Abteilung\Alt` und alles darunter wird nicht mehr als DataArea geliefert
|
||||
- unterhalb von `Abteilung\Alt` wird auch nicht weiter traversiert
|
||||
- andere Teilbaeume bleiben unveraendert sichtbar
|
||||
|
||||
### 2. Bestimmte Ordnernamen ueberall ausschliessen
|
||||
|
||||
Konfiguration:
|
||||
|
||||
```text
|
||||
NtfsExcludePaths=*\Temp;*\Archiv
|
||||
```
|
||||
|
||||
Wirkung:
|
||||
|
||||
- jeder Pfad, dessen letzter oder einer spaeteren Segmente `Temp` oder `Archiv` entspricht, wird ausgeschlossen
|
||||
- das ist praktisch fuer technische oder historische Unterordner, die in vielen Shares gleich benannt sind
|
||||
|
||||
### 3. Nur einen Fachbereich sichtbar lassen
|
||||
|
||||
Konfiguration:
|
||||
|
||||
```text
|
||||
NtfsIncludePaths=Fachbereiche\HR\*
|
||||
```
|
||||
|
||||
Wirkung:
|
||||
|
||||
- nur Pfade unterhalb von `Fachbereiche\HR` werden als DataAreas geliefert
|
||||
- notwendige Zwischenpfade wie `Fachbereiche` duerfen fuer die Traversierung erhalten bleiben
|
||||
- alle anderen Teilbaeume unterhalb von `RootPath` fallen aus der Ergebnismenge
|
||||
|
||||
### 4. Whitelist und Blacklist kombinieren
|
||||
|
||||
Konfiguration:
|
||||
|
||||
```text
|
||||
NtfsIncludePaths=Fachbereiche\IT\*
|
||||
NtfsExcludePaths=Fachbereiche\IT\Test;Fachbereiche\IT\Alt\*
|
||||
```
|
||||
|
||||
Wirkung:
|
||||
|
||||
- grundsaetzlich ist nur `Fachbereiche\IT` relevant
|
||||
- innerhalb dieses Bereichs werden `Test` und der komplette Teilbaum `Alt` wieder ausgeschlossen
|
||||
- Exclude gewinnt also auch innerhalb eines eingeschraenkten Include-Bereichs
|
||||
|
||||
### 5. Absoluten UNC-Pfad fuer DFS-Link verwenden
|
||||
|
||||
Konfiguration:
|
||||
|
||||
```text
|
||||
NtfsIncludePaths=\\server\dfs\namespace\link\Produktion\*
|
||||
```
|
||||
|
||||
Wirkung:
|
||||
|
||||
- die Regel greift auch dann, wenn der Root ueber DFS klassifiziert wird
|
||||
- benoetigte Vorpfade wie `\\server\dfs`, `\\server\dfs\namespace` und `\\server\dfs\namespace\link` duerfen fuer die Traversierung erhalten bleiben
|
||||
- dadurch kann ein bestimmter DFS-Zweig sehr gezielt freigegeben werden
|
||||
|
||||
### 6. Nur bestimmte Shares unter einem Server-Root zulassen
|
||||
|
||||
Konfiguration:
|
||||
|
||||
```text
|
||||
NtfsIncludePaths=ShareA\*;ShareB\*
|
||||
```
|
||||
|
||||
Voraussetzung:
|
||||
|
||||
- `RootPath` zeigt auf einen Server-Root wie `\\fileserver`
|
||||
|
||||
Wirkung:
|
||||
|
||||
- nur Kinder unterhalb von `ShareA` und `ShareB` werden sichtbar
|
||||
- andere Shares des Servers werden nicht materialisiert und nicht weiter traversiert
|
||||
|
||||
### 7. Direktes Laden eines ausgeschlossenen Pfads
|
||||
|
||||
Konfiguration:
|
||||
|
||||
```text
|
||||
NtfsExcludePaths=Abteilung\Alt\*
|
||||
```
|
||||
|
||||
Wirkung:
|
||||
|
||||
- ein direkter `LoadDataArea()` auf einen Pfad unterhalb von `Abteilung\Alt` liefert kein Objekt mehr
|
||||
- der konfigurierte `RootPath` selbst bleibt davon ausgenommen und kann weiterhin geladen werden
|
||||
|
||||
## Technische Einhaengepunkte
|
||||
|
||||
### 1. Provider-seitige Policy-Methoden
|
||||
|
||||
Im NTFS-Provider existiert dafuer inzwischen eine kleine Policy-Schicht, insbesondere:
|
||||
|
||||
- `GetAdditionalConfigurationValues(string key)`
|
||||
- `ShouldIncludeDataArea(...)`
|
||||
- `ShouldTraverseDataArea(...)`
|
||||
- `MatchesPathPolicy(...)`
|
||||
- `TryMatchPathPolicy(...)`
|
||||
- `CanPathLeadToPattern(...)`
|
||||
|
||||
Die Methode `IsAdditionalConfigurationEnabled()` bleibt fuer boolesche Flags bestehen und wird durch Listen-/String-Helfer ergaenzt.
|
||||
|
||||
### 2. Anwendung in der DataArea-Traversierung
|
||||
|
||||
Der erste und wichtigste Einhaengepunkt ist `GetChildDataAreasAsync()`.
|
||||
|
||||
Dort wird heute jede gefundene Ebene verarbeitet und vor `BuildDataAreaAsync()` sowie vor dem rekursiven Abstieg durch die Path-Policy geprueft.
|
||||
|
||||
Vorteil:
|
||||
|
||||
- geringe Eingriffstiefe
|
||||
- kein Umbau der allgemeinen NTFS-Basis erforderlich
|
||||
- fachliche Wirkung genau dort, wo DataAreas erzeugt werden
|
||||
|
||||
### 3. Wiederverwendung fuer Permission-Management
|
||||
|
||||
`IsPermissionManagedFolderPath()` bleibt fachlich auf Folder beschraenkt, verwendet fuer Black-/Whitelist aber dieselbe generische Path-Policy.
|
||||
|
||||
Damit wird vermieden, dass ein Ordner zwar nicht mehr als DataArea sichtbar ist, aber weiterhin im Permission-Flow auftaucht.
|
||||
|
||||
### 4. Wiederverwendung fuer `LoadDataArea()`
|
||||
|
||||
Auch `LoadDataArea()` verwendet die Path-Policy inzwischen, damit gefilterte Pfade nicht per Direktzugriff wieder sichtbar werden.
|
||||
|
||||
## Offene Abgrenzung
|
||||
|
||||
Bewusst weiterhin nicht umgesetzt:
|
||||
|
||||
- Umbau von `cNtfsBase` auf generische Filter-Callbacks
|
||||
- freie Regex-Konfiguration in `AdditionalConfiguration`
|
||||
- unterschiedliche Regeln je Klassifikationstyp
|
||||
|
||||
Diese Themen koennen spaeter folgen, sind fuer den aktuellen Nutzen aber nicht noetig.
|
||||
|
||||
## Logging
|
||||
|
||||
Fuer ausgeschlossene Pfade sollte auf `Debug` geloggt werden:
|
||||
|
||||
- welcher Pfad verworfen wurde
|
||||
- welche Regel gegriffen hat
|
||||
- ob der Pfad nur nicht materialisiert oder auch nicht traversiert wurde
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
Skip NTFS path '\\server\share\IT\_disabled' due to AdditionalConfiguration rule 'NtfsExcludePaths=IT\_disabled'
|
||||
```
|
||||
|
||||
Das ist wichtig, damit fehlende DataAreas spaeter im Betrieb nachvollziehbar bleiben.
|
||||
|
||||
## Empfohlener Umsetzungsplan
|
||||
|
||||
1. Listenparser fuer `AdditionalConfiguration` im NTFS-Provider einfuehren.
|
||||
2. Pfadnormalisierung und Matching fuer relative sowie absolute Pfade kapseln.
|
||||
3. `GetChildDataAreasAsync()` um `ShouldTraverseDataArea(...)` erweitern.
|
||||
4. Debug-Logging fuer Skip-Faelle einfuehren.
|
||||
5. Dieselbe Policy in `IsPermissionManagedFolderPath()` und `LoadDataArea()` wiederverwenden.
|
||||
|
||||
Diese Punkte sind im aktuellen Implementierungsstand umgesetzt.
|
||||
|
||||
## Kurzfazit
|
||||
|
||||
Die generische Path-Policy im NTFS-Provider ist klein genug fuer eine risikoarme Implementierung, passt in die vorhandene `AdditionalConfiguration`-Architektur und arbeitet ohne Sonderregeln pro Klassifikationstyp.
|
||||
Reference in New Issue
Block a user