| | | 1 | | using Elsa.ExternalAuthentication.Contracts; |
| | | 2 | | using Elsa.ExternalAuthentication.Models; |
| | | 3 | | using Elsa.ExternalAuthentication.Options; |
| | | 4 | | using Elsa.ExternalAuthentication.Permissions; |
| | | 5 | | using Microsoft.Extensions.Options; |
| | | 6 | | |
| | | 7 | | namespace Elsa.ExternalAuthentication.Services; |
| | | 8 | | |
| | | 9 | | public sealed class DefaultPermissionGrantResolver( |
| | | 10 | | IEnumerable<IPermissionGrantSource> sources, |
| | | 11 | | IPermissionDescriptorRegistry descriptors, |
| | | 12 | | IOptions<ExternalAuthenticationOptions> options) : IPermissionGrantResolver |
| | | 13 | | { |
| | | 14 | | private readonly IReadOnlyDictionary<string, IPermissionGrantSource> _sources = sources.ToDictionary(x => x.Type, St |
| | | 15 | | |
| | | 16 | | public async ValueTask<PermissionGrantResult> ResolveAsync(PermissionGrantResolutionContext context, CancellationTok |
| | | 17 | | { |
| | | 18 | | var grants = new List<PermissionGrant>(); |
| | | 19 | | var warnings = new List<PermissionGrantWarning>(); |
| | | 20 | | var warningKeys = new HashSet<(string Code, string Message)>(); |
| | | 21 | | var knownPermissions = descriptors.List().Select(x => x.Name).ToHashSet(StringComparer.Ordinal); |
| | | 22 | | var boundary = new PermissionGrantBoundary(options.Value.PermissionGrants); |
| | | 23 | | |
| | | 24 | | foreach (var selection in context.Connection.Connection.PermissionGrantSources.OrderBy(x => x.Order).ThenBy(x => |
| | | 25 | | { |
| | | 26 | | if (!IsAllowedSource(selection.Type) || !_sources.TryGetValue(selection.Type, out var source)) |
| | | 27 | | { |
| | | 28 | | AddWarning(warnings, warningKeys, new PermissionGrantWarning("permission_grant_source_unavailable", $"Th |
| | | 29 | | continue; |
| | | 30 | | } |
| | | 31 | | |
| | | 32 | | var result = await source.GetGrantsAsync(new PermissionGrantContext(context.TargetTenantId, context.UserId, |
| | | 33 | | foreach (var warning in result.Warnings) |
| | | 34 | | AddWarning(warnings, warningKeys, warning); |
| | | 35 | | foreach (var grant in result.Grants) |
| | | 36 | | { |
| | | 37 | | if (!boundary.Allows(grant.Permission)) |
| | | 38 | | { |
| | | 39 | | AddWarning(warnings, warningKeys, new PermissionGrantWarning("permission_denied_by_deployment", $"Th |
| | | 40 | | continue; |
| | | 41 | | } |
| | | 42 | | |
| | | 43 | | if (!knownPermissions.Contains(grant.Permission)) |
| | | 44 | | AddWarning(warnings, warningKeys, new PermissionGrantWarning("unknown_permission_descriptor", $"No m |
| | | 45 | | |
| | | 46 | | if (grants.All(x => !string.Equals(x.Permission, grant.Permission, StringComparison.Ordinal))) |
| | | 47 | | grants.Add(grant); |
| | | 48 | | } |
| | | 49 | | } |
| | | 50 | | |
| | | 51 | | return new PermissionGrantResult(grants, warnings); |
| | | 52 | | } |
| | | 53 | | |
| | | 54 | | private bool IsAllowedSource(string type) => options.Value.AllowedPermissionGrantSourceTypes.Count == 0 || options.V |
| | | 55 | | private static void AddWarning(ICollection<PermissionGrantWarning> warnings, ISet<(string Code, string Message)> key |
| | | 56 | | { |
| | | 57 | | if (keys.Add((warning.Code, warning.Message))) |
| | | 58 | | warnings.Add(warning); |
| | | 59 | | } |
| | | 60 | | } |
| | | 61 | | |
| | | 62 | | public sealed class DefaultPermissionDelegationAuthorizer(IOptions<ExternalAuthenticationOptions> options) : IPermission |
| | | 63 | | { |
| | | 64 | | public ValueTask<PermissionDelegationResult> AuthorizeAsync(System.Security.Claims.ClaimsPrincipal actor, IReadOnlyC |
| | | 65 | | { |
| | | 66 | | cancellationToken.ThrowIfCancellationRequested(); |
| | | 67 | | var actorPermissions = actor.FindAll(PermissionNames.ClaimType).Select(x => x.Value).ToHashSet(StringComparer.Or |
| | | 68 | | var boundary = new PermissionGrantBoundary(options.Value.PermissionGrants); |
| | | 69 | | var configuredPermissions = selections.SelectMany(x => PermissionGrantMappingSettings.Read(x.Settings)).SelectMa |
| | | 70 | | .Concat(selections.Where(x => string.Equals(x.Type, ClaimPassThroughPermissionGrantSource.SourceType, String |
| | | 71 | | .Distinct(StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray(); |
| | | 72 | | var unrestricted = actorPermissions.Contains(PermissionNames.All) || actorPermissions.Contains(Permissions.Exter |
| | | 73 | | var mayDelegate = unrestricted || actorPermissions.Contains(Permissions.ExternalAuthenticationPermissions.Permis |
| | | 74 | | var unauthorized = configuredPermissions.Where(permission => !boundary.Allows(permission) || !mayDelegate || (!u |
| | | 75 | | return ValueTask.FromResult(new PermissionDelegationResult(unauthorized.Length == 0, unauthorized)); |
| | | 76 | | } |
| | | 77 | | } |
| | | 78 | | |
| | 14 | 79 | | internal sealed class PermissionGrantBoundary(PermissionGrantOptions options) |
| | | 80 | | { |
| | 14 | 81 | | private readonly IReadOnlySet<string> _allowed = options.AllowedPermissions.ToHashSet(StringComparer.Ordinal); |
| | 14 | 82 | | private readonly IReadOnlySet<string> _denied = options.DeniedPermissions.ToHashSet(StringComparer.Ordinal); |
| | 20 | 83 | | public bool Allows(string permission) => !string.IsNullOrWhiteSpace(permission) && !_denied.Contains(permission) && |
| | | 84 | | } |