Skip to content

Commit

Permalink
Declarative Authorization, step 3. #20
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Jan 24, 2024
1 parent 7602796 commit 16d4dfe
Show file tree
Hide file tree
Showing 91 changed files with 1,854 additions and 1,393 deletions.
2 changes: 1 addition & 1 deletion docs/decisions/0100-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ The options are:
`Custom Implementation`

- Can support many authorization protocols, like: HMAC, Basic, APIKey, Claims, Cookies, etc. as prescribed by 3rd party integrations and web hooks.
- Can support many authorization assertions, like: Roles (RBAC), FeatureLevel access, etc.
- Can support many authorization assertions, like: Roles (RBAC), Features access, etc.
- Can support SSO authentication from 3rd parties, like: Microsoft, Google, Facebook etc.
- Would not be OIDC authentication compliant at first, but could be made to be OIDC compliant later, by either integrating with an external provider or implementing the endpoints and flows.
- No additional operational costs, (unlike IdentityServer, Auth0 require etc)
Expand Down
4 changes: 2 additions & 2 deletions src/ApiHost1/Api/TestingOnly/TestingWebApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ public async Task<ApiResult<string, GetCallerTestingOnlyResponse>> AuthZByRole(
{ CallerId = _contextFactory.Create().CallerId });
}

public async Task<ApiResult<string, GetCallerTestingOnlyResponse>> AuthZByFeatureLevel(
AuthorizeByFeatureLevelTestingOnlyRequest request, CancellationToken cancellationToken)
public async Task<ApiResult<string, GetCallerTestingOnlyResponse>> AuthZByFeature(
AuthorizeByFeatureTestingOnlyRequest request, CancellationToken cancellationToken)
{
await Task.CompletedTask;
return () => new Result<GetCallerTestingOnlyResponse, Error>(new GetCallerTestingOnlyResponse
Expand Down
2 changes: 1 addition & 1 deletion src/ApiHost1/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"ASPNETCORE_ENVIRONMENT": "Production"
}
},
"ApiHandler-SourceGenerator-Development": {
"ApiHandler-SourceGenerators-Development-Development": {
"commandName": "DebugRoslynComponent",
"targetProject": "../ApiHost1/ApiHost1.csproj"
}
Expand Down
26 changes: 13 additions & 13 deletions src/Application.Common.UnitTests/CallerSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public void WhenCreateAsAnonymous_ThenReturnsANewCallForAnonymousCaller()
result.Authorization.Should().BeNone();
result.IsAuthenticated.Should().BeFalse();
result.Roles.All.Should().BeEmpty();
result.FeatureLevels.All.Should().ContainSingle(PlatformFeatureLevels.Basic.Name);
result.Features.All.Should().ContainSingle(PlatformFeatures.Basic.Name);
result.TenantId.Should().BeNull();
result.CallerId.Should().Be(CallerConstants.AnonymousUserId);
result.CallId.Should().NotBeNull();
Expand All @@ -35,7 +35,7 @@ public void WhenCreateAsAnonymousTenant_ThenReturnsANewCallForAnonymousTenantedC
result.Authorization.Should().BeNone();
result.IsAuthenticated.Should().BeFalse();
result.Roles.All.Should().BeEmpty();
result.FeatureLevels.All.Should().ContainSingle(PlatformFeatureLevels.Basic.Name);
result.Features.All.Should().ContainSingle(PlatformFeatures.Basic.Name);
result.TenantId.Should().Be("atenantid");
result.CallerId.Should().Be(CallerConstants.AnonymousUserId);
result.CallId.Should().NotBeNullOrEmpty();
Expand All @@ -55,7 +55,7 @@ public void WhenCreateAsCallerFromCall_ThenReturnsACustomCaller()
result.Authorization.Should().BeNone();
result.IsAuthenticated.Should().BeFalse();
result.Roles.All.Should().BeEmpty();
result.FeatureLevels.All.Should().ContainSingle(PlatformFeatureLevels.Basic.Name);
result.Features.All.Should().ContainSingle(PlatformFeatures.Basic.Name);
result.TenantId.Should().BeNull();
result.CallerId.Should().Be("acallerid");
result.CallId.Should().Be("acallid");
Expand All @@ -69,8 +69,8 @@ public void WhenCreateAsExternalWebHook_ThenReturnsWebhookServiceAccountCaller()
result.IsServiceAccount.Should().BeTrue();
result.Authorization.Should().BeNone();
result.IsAuthenticated.Should().BeTrue();
result.Roles.All.Should().ContainSingle(PlatformRoles.ServiceAccount);
result.FeatureLevels.All.Should().ContainSingle(PlatformFeatureLevels.Basic.Name);
result.Roles.All.Should().ContainSingle(rol => rol == PlatformRoles.ServiceAccount);
result.Features.All.Should().ContainSingle(PlatformFeatures.Basic.Name);
result.TenantId.Should().BeNull();
result.CallerId.Should().Be(CallerConstants.ExternalWebhookAccountUserId);
result.CallId.Should().Be("acallid");
Expand All @@ -84,8 +84,8 @@ public void WhenCreateAsMaintenanceWithNoCall_ThenReturnsMaintenanceServiceAccou
result.IsServiceAccount.Should().BeTrue();
result.Authorization.Should().BeNone();
result.IsAuthenticated.Should().BeTrue();
result.Roles.All.Should().ContainSingle(PlatformRoles.ServiceAccount);
result.FeatureLevels.All.Should().ContainSingle(PlatformFeatureLevels.Paid2.Name);
result.Roles.All.Should().ContainSingle(rol => rol == PlatformRoles.ServiceAccount);
result.Features.All.Should().ContainSingle(PlatformFeatures.Paid2.Name);
result.TenantId.Should().BeNull();
result.CallerId.Should().Be(CallerConstants.MaintenanceAccountUserId);
result.CallId.Should().NotBeNullOrEmpty();
Expand All @@ -99,8 +99,8 @@ public void WhenCreateAsMaintenance_ThenReturnsMaintenanceServiceAccountWithAllF
result.IsServiceAccount.Should().BeTrue();
result.Authorization.Should().BeNone();
result.IsAuthenticated.Should().BeTrue();
result.Roles.All.Should().ContainSingle(PlatformRoles.ServiceAccount);
result.FeatureLevels.All.Should().ContainSingle(PlatformFeatureLevels.Paid2.Name);
result.Roles.All.Should().ContainSingle(rol => rol == PlatformRoles.ServiceAccount);
result.Features.All.Should().ContainSingle(PlatformFeatures.Paid2.Name);
result.TenantId.Should().BeNull();
result.CallerId.Should().Be(CallerConstants.MaintenanceAccountUserId);
result.CallId.Should().Be("acallid");
Expand All @@ -114,8 +114,8 @@ public void WhenCreateAsMaintenanceTenant_ThenReturnsMaintenanceServiceAccountWi
result.IsServiceAccount.Should().BeTrue();
result.Authorization.Should().BeNone();
result.IsAuthenticated.Should().BeTrue();
result.Roles.All.Should().ContainSingle(PlatformRoles.ServiceAccount);
result.FeatureLevels.All.Should().ContainSingle(PlatformFeatureLevels.Paid2.Name);
result.Roles.All.Should().ContainSingle(rol => rol == PlatformRoles.ServiceAccount);
result.Features.All.Should().ContainSingle(PlatformFeatures.Paid2.Name);
result.TenantId.Should().Be("atenantid");
result.CallerId.Should().Be(CallerConstants.MaintenanceAccountUserId);
result.CallId.Should().NotBeNullOrEmpty();
Expand All @@ -129,8 +129,8 @@ public void WhenCreateAsServiceClient_ThenReturnsServiceClientCaller()
result.IsServiceAccount.Should().BeTrue();
result.Authorization.Should().BeNone();
result.IsAuthenticated.Should().BeTrue();
result.Roles.All.Should().ContainSingle(PlatformRoles.ServiceAccount);
result.FeatureLevels.All.Should().ContainSingle(PlatformFeatureLevels.Paid2.Name);
result.Roles.All.Should().ContainSingle(rol => rol == PlatformRoles.ServiceAccount);
result.Features.All.Should().ContainSingle(PlatformFeatures.Paid2.Name);
result.TenantId.Should().BeNull();
result.CallerId.Should().Be(CallerConstants.ServiceClientAccountUserId);
result.CallId.Should().NotBeNullOrEmpty();
Expand Down
24 changes: 12 additions & 12 deletions src/Application.Common/Caller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ public AnonymousCaller(string? tenantId = null)
{
TenantId = tenantId;
Roles = new ICallerContext.CallerRoles();
FeatureLevels = new ICallerContext.CallerFeatureLevels(new[] { PlatformFeatureLevels.Basic }, null);
Features = new ICallerContext.CallerFeatures(new[] { PlatformFeatures.Basic }, null);
}

public ICallerContext.CallerRoles Roles { get; }

public ICallerContext.CallerFeatureLevels FeatureLevels { get; }
public ICallerContext.CallerFeatures Features { get; }

public Optional<ICallerContext.CallerAuthorization> Authorization =>
Optional<ICallerContext.CallerAuthorization>.None;
Expand All @@ -131,14 +131,14 @@ public MaintenanceAccountCaller(string? callId = null, string? tenantId = null)
CallId = callId ?? GenerateCallId();
TenantId = tenantId;
Roles = new ICallerContext.CallerRoles(new[] { PlatformRoles.ServiceAccount }, null);
FeatureLevels =
new ICallerContext.CallerFeatureLevels(
new[] { PlatformFeatureLevels.Paid2 }, null);
Features =
new ICallerContext.CallerFeatures(
new[] { PlatformFeatures.Paid2 }, null);
}

public ICallerContext.CallerRoles Roles { get; }

public ICallerContext.CallerFeatureLevels FeatureLevels { get; }
public ICallerContext.CallerFeatures Features { get; }

public Optional<ICallerContext.CallerAuthorization> Authorization =>
Optional<ICallerContext.CallerAuthorization>.None;
Expand All @@ -161,8 +161,8 @@ private sealed class ServiceClientAccountCaller : ICallerContext
{
public ICallerContext.CallerRoles Roles { get; } = new(new[] { PlatformRoles.ServiceAccount }, null);

public ICallerContext.CallerFeatureLevels FeatureLevels { get; } = new(
new[] { PlatformFeatureLevels.Paid2 }, null);
public ICallerContext.CallerFeatures Features { get; } = new(
new[] { PlatformFeatures.Paid2 }, null);

public Optional<ICallerContext.CallerAuthorization> Authorization =>
Optional<ICallerContext.CallerAuthorization>.None;
Expand All @@ -187,12 +187,12 @@ public ExternalWebHookAccountCaller(string? callId = null)
{
CallId = callId ?? GenerateCallId();
Roles = new ICallerContext.CallerRoles(new[] { PlatformRoles.ServiceAccount }, null);
FeatureLevels = new ICallerContext.CallerFeatureLevels(new[] { PlatformFeatureLevels.Basic }, null);
Features = new ICallerContext.CallerFeatures(new[] { PlatformFeatures.Basic }, null);
}

public ICallerContext.CallerRoles Roles { get; }

public ICallerContext.CallerFeatureLevels FeatureLevels { get; }
public ICallerContext.CallerFeatures Features { get; }

public Optional<ICallerContext.CallerAuthorization> Authorization =>
Optional<ICallerContext.CallerAuthorization>.None;
Expand All @@ -219,12 +219,12 @@ public CustomCaller(ICallContext call)
CallId = call.CallId;
TenantId = call.TenantId;
Roles = new ICallerContext.CallerRoles();
FeatureLevels = new ICallerContext.CallerFeatureLevels(new[] { PlatformFeatureLevels.Basic }, null);
Features = new ICallerContext.CallerFeatures(new[] { PlatformFeatures.Basic }, null);
}

public ICallerContext.CallerRoles Roles { get; }

public ICallerContext.CallerFeatureLevels FeatureLevels { get; }
public ICallerContext.CallerFeatures Features { get; }

public Optional<ICallerContext.CallerAuthorization> Authorization =>
Optional<ICallerContext.CallerAuthorization>.None;
Expand Down
26 changes: 13 additions & 13 deletions src/Application.Interfaces/ICallerContext.RolesAndFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,41 @@ public class CallerRoles
{
public CallerRoles()
{
All = Array.Empty<string>();
Platform = Array.Empty<string>();
Organization = Array.Empty<string>();
All = Array.Empty<RoleLevel>();
Platform = Array.Empty<RoleLevel>();
Organization = Array.Empty<RoleLevel>();
}

public CallerRoles(string[]? platform, string[]? member)
public CallerRoles(RoleLevel[]? platform, RoleLevel[]? member)
{
Platform = platform ?? Array.Empty<string>();
Organization = member ?? Array.Empty<string>();
Platform = platform ?? Array.Empty<RoleLevel>();
Organization = member ?? Array.Empty<RoleLevel>();
All = Platform
.Concat(Organization)
.Distinct()
.ToArray();
}

public string[] All { get; }
public RoleLevel[] All { get; }

public string[] Organization { get; }
public RoleLevel[] Organization { get; }

public string[] Platform { get; }
public RoleLevel[] Platform { get; }
}

/// <summary>
/// Defines the sets of features that a caller can have
/// Defines the authorization features that a caller can have
/// </summary>
public class CallerFeatureLevels
public class CallerFeatures
{
public CallerFeatureLevels()
public CallerFeatures()
{
All = Array.Empty<FeatureLevel>();
Platform = Array.Empty<FeatureLevel>();
Organization = Array.Empty<FeatureLevel>();
}

public CallerFeatureLevels(FeatureLevel[]? platform, FeatureLevel[]? member)
public CallerFeatures(FeatureLevel[]? platform, FeatureLevel[]? member)
{
Platform = platform ?? Array.Empty<FeatureLevel>();
Organization = member ?? Array.Empty<FeatureLevel>();
Expand Down
4 changes: 2 additions & 2 deletions src/Application.Interfaces/ICallerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ public enum AuthorizationMethod
string CallId { get; }

/// <summary>
/// The feature sets belonging to the caller
/// The authorization features belonging to the caller
/// </summary>
CallerFeatureLevels FeatureLevels { get; }
CallerFeatures Features { get; }

/// <summary>
/// Whether the called is authenticated or not
Expand Down
2 changes: 1 addition & 1 deletion src/Application.Resources.Shared/EndUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class EndUser : IIdentifiableResource

public EndUserClassification Classification { get; set; }

public List<string> FeatureLevels { get; set; } = new();
public List<string> Features { get; set; } = new();

public List<string> Roles { get; set; } = new();

Expand Down
1 change: 1 addition & 0 deletions src/Common/Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPlatformProject>true</IsPlatformProject>
<DefineConstants>COMMON_PROJECT</DefineConstants>
</PropertyGroup>

<ItemGroup>
Expand Down
21 changes: 17 additions & 4 deletions src/Common/Extensions/CollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#if COMMON_PROJECT
using System.Text;
using JetBrains.Annotations;
#endif

namespace Common.Extensions;

#if COMMON_PROJECT
[UsedImplicitly]
#endif

public static class CollectionExtensions
{
#if COMMON_PROJECT || GENERATORS_WEB_API_PROJECT
/// <summary>
/// Whether the <see cref="target" /> string exists in the <see cref="collection" />
/// </summary>
Expand All @@ -18,15 +24,17 @@ public static bool ContainsIgnoreCase(this IEnumerable<string> collection, strin

return collection.Any(item => item.EqualsIgnoreCase(target));
}

#endif
#if COMMON_PROJECT
/// <summary>
/// Returns the first item in the collection
/// </summary>
public static TResult First<TResult>(this IReadOnlyList<TResult> list)
{
return list[0];
}

#endif
#if COMMON_PROJECT || GENERATORS_WEB_API_PROJECT
/// <summary>
/// Whether the collection contains any items
/// </summary>
Expand All @@ -36,8 +44,11 @@ public static bool HasAny<T>(this IEnumerable<T>? collection)
{
return false;
}

#if COMMON_PROJECT
return !collection.HasNone();
#elif GENERATORS_WEB_API_PROJECT
return !collection!.HasNone();
#endif
}

/// <summary>
Expand All @@ -47,7 +58,8 @@ public static bool HasNone<T>(this IEnumerable<T> collection)
{
return !collection.Any();
}

#endif
#if COMMON_PROJECT
/// <summary>
/// Joins all values separated by the <see cref="separator" />
/// </summary>
Expand Down Expand Up @@ -82,4 +94,5 @@ public static bool NotContainsIgnoreCase(this IEnumerable<string> collection, st
{
return !collection.ContainsIgnoreCase(target);
}
#endif
}
Loading

0 comments on commit 16d4dfe

Please sign in to comment.