diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 55e26b7..630d960 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -8,21 +8,31 @@ on:
jobs:
verify_tag:
- name: Verify tag 'release-*' on the head
+ name: Verify tag 'release-*' exists on the current commit
runs-on: ubuntu-latest
permissions:
contents: read
-
+
steps:
- uses: actions/checkout@v6
- - name: Abort
- if: github.ref != 'refs/heads/main' || !startsWith(github.ref, 'refs/tags/release-')
- run: exit 1
-
- - name: Success
- if: github.ref == 'refs/heads/main' && startsWith(github.ref, 'refs/tags/release-')
- run: exit 0
+ - name: Check for release tag on current commit
+ run: |
+ # Get current commit hash
+ CURRENT_COMMIT=$(git rev-parse HEAD)
+
+ # Check if any release-* tag points to current commit
+ RELEASE_TAG=$(git tag --points-at $CURRENT_COMMIT | grep '^release-' | head -1)
+
+ if [ -z "$RELEASE_TAG" ]; then
+ echo "❌ No release-* tag found on current commit $CURRENT_COMMIT"
+ echo "Available tags on this commit:"
+ git tag --points-at $CURRENT_COMMIT || echo "No tags found"
+ exit 1
+ else
+ echo "✅ Found release tag: $RELEASE_TAG on commit $CURRENT_COMMIT"
+ echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV
+ fi
verify_release_notes:
name: Verify RELEASE_NOTES.md has been changed before Release
diff --git a/.gitignore b/.gitignore
index bf3675e..bd559ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,5 @@ bld/
[Bb]in/
[Oo]bj/
-AutoBackend.sln.DotSettings.user
\ No newline at end of file
+AutoBackend.sln.DotSettings.user
+.idea
\ No newline at end of file
diff --git a/.idea/.idea.AutoBackend/.idea/.gitignore b/.idea/.idea.AutoBackend/.idea/.gitignore
deleted file mode 100644
index 8ce2ff0..0000000
--- a/.idea/.idea.AutoBackend/.idea/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Rider ignored files
-/projectSettingsUpdater.xml
-/modules.xml
-/contentModel.xml
-/.idea.AutoBackend.iml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/.idea.AutoBackend/.idea/.name b/.idea/.idea.AutoBackend/.idea/.name
deleted file mode 100644
index d2c5a76..0000000
--- a/.idea/.idea.AutoBackend/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-AutoBackend
\ No newline at end of file
diff --git a/.idea/.idea.AutoBackend/.idea/encodings.xml b/.idea/.idea.AutoBackend/.idea/encodings.xml
deleted file mode 100644
index df87cf9..0000000
--- a/.idea/.idea.AutoBackend/.idea/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.AutoBackend/.idea/indexLayout.xml b/.idea/.idea.AutoBackend/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163..0000000
--- a/.idea/.idea.AutoBackend/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.AutoBackend/.idea/jsonSchemas.xml b/.idea/.idea.AutoBackend/.idea/jsonSchemas.xml
deleted file mode 100644
index 13038b0..0000000
--- a/.idea/.idea.AutoBackend/.idea/jsonSchemas.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.AutoBackend/.idea/markdown.xml b/.idea/.idea.AutoBackend/.idea/markdown.xml
deleted file mode 100644
index f6d2542..0000000
--- a/.idea/.idea.AutoBackend/.idea/markdown.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.AutoBackend/.idea/vcs.xml b/.idea/.idea.AutoBackend/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/.idea.AutoBackend/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index b5510ab..0f1b2b6 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [](https://www.myget.org/feed/autobackend-dev/package/nuget/AutoBackend.SDK) | [](https://www.myget.org/feed/autobackend/package/nuget/AutoBackend.SDK) [](https://www.nuget.org/packages/AutoBackend.SDK) |
-This package provides infrastructure templates for facilitating the creation of backend services.
-It is a personal project without a commercial foundation and offers no guarantees regarding future development.
+This package provides infrastructure templates to facilitate the creation of backend services.
+This is a personal project with no commercial backing and offers no guarantees regarding future development.
# Features
@@ -24,12 +24,11 @@ It is a personal project without a commercial foundation and offers no guarantee
- [Mutations](#mutations)
- [Modeling](#modeling)
- [Filtering](#filtering)
- - _Authorization: currently unsupported (may be introduced in future releases)_
+ - [Authorization](#authorization)
# Initialization
A sample usage scenario is available [here](src/samples/Sample).
-Copies of those samples are also provided.
## Initialize AutoBackend from your Program.cs file
@@ -80,13 +79,15 @@ public class Participating
}
```
-## Configuration of all features is detailed below
+## Feature Configuration
+
+All features are detailed in the sections below.
---
# Database
-Currently supported relational databases (including in-memory) are:
+The following relational databases are currently supported (including in-memory):
- SqlServer
- Postgres
@@ -101,8 +102,7 @@ There are typically three types of database tables:
- Single-column primary key,
- Multi-column primary key.
-Depending on the number of columns in your entity, follow the instructions below to enable AutoBackend.SDK to track
-changes.
+Depending on the number of primary key columns in your entity, follow the instructions below to enable AutoBackend.SDK to track changes.
### Keyless Entities
@@ -118,8 +118,7 @@ public class TransactionVersion
### Single-PK Entities
-Use the `[GenericEntity()]` attribute to designate a model as an entity with a single
-property primary key.
+Use the `[GenericEntity()]` attribute to designate a model as an entity with a single-column primary key.
```csharp
[GenericEntity(
@@ -140,7 +139,7 @@ primary key.
> Currently, the maximum number of properties in a composite key is **8**.
>
-> It is anticipated that this limit will suffice for most use cases.
+> This limit should be sufficient for most use cases.
```csharp
[GenericEntity(
@@ -159,7 +158,7 @@ public class Participating
## Providers
-For database connection management, configure the "Database" section.
+To configure database connection management, set up the "Database" section in your configuration.
```json
"Database": {
@@ -178,29 +177,23 @@ The `PrimaryProvider` property accepts one of the following string values:
- `SqlServer`,
- `Postgres`.
-The `Providers` property must contain an object with optional properties: `InMemory`, `SqlServer`, or `Postgres`. The
-property corresponding to the chosen `PrimaryProvider` value is mandatory.
+The `Providers` property must contain an object with optional properties: `InMemory`, `SqlServer`, or `Postgres`. The property corresponding to the selected `PrimaryProvider` value is required.
## Migrations
First, [install Entity Framework Core Tools](https://learn.microsoft.com/en-us/ef/core/cli/dotnet).
-You can create a new migration with one of the following commands executed from the project root directory.
-
-`dotnet ef migrations add "" -o Migrations/SqlServer -c SqlServerGenericDbContext` for SqlServer.
+You can create a new migration by executing one of the following commands from the project root directory:
-`dotnet ef migrations add "" -o Migrations/Postgres -c PostgresGenericDbContext` for Postgres.
+- For SqlServer: `dotnet ef migrations add "" -o Migrations/SqlServer -c SqlServerGenericDbContext`
+- For Postgres: `dotnet ef migrations add "" -o Migrations/Postgres -c PostgresGenericDbContext`
-Alternatively, use scripts [to add a new migration](scripts/add_migration.sh)
-or [to remove the last migration](scripts/remove_migration.sh) for both database providers.
+Alternatively, you can use the provided scripts [to add a new migration](scripts/add_migration.sh) or [to remove the last migration](scripts/remove_migration.sh) for both database providers.
-If you opt not to have AutoBackend manage database migrations (see above), you can perform migrations manually by
-executing `dotnet ef database update` from the project root directory.
+If you choose not to use AutoBackend's migration management (see above), you can perform migrations manually by executing `dotnet ef database update` from the project root directory.
### Migrate on Startup
-If using a relational database (e.g., SqlServer or Postgres), you can configure AutoBackend to either automatically
-migrate the database at application startup or not, by passing the `migrateRelationalOnStartup` parameter to
-the `RunAsync` method where AutoBackend is initialized. For example:
+When using a relational database (e.g., SqlServer or Postgres), you can configure AutoBackend to automatically migrate the database at application startup by passing the `migrateRelationalOnStartup` parameter to the `RunAsync` method during AutoBackend initialization. For example:
```csharp
await new AutoBackend.Sdk.AutoBackendHost().RunAsync(args, migrateRelationalOnStartup: true);
@@ -208,14 +201,14 @@ await new AutoBackend.Sdk.AutoBackendHost().RunAsync(args, migrateRelat
# API
-AutoBackend.SDK currently supports two types of application interfaces:
+AutoBackend.SDK currently supports two types of API interfaces:
-- Traditional HTTP API
-- Basic GraphQL
+- HTTP REST API
+- GraphQL
## HTTP API
-Use the `[GenericController]` attribute on models to generate HTTP API endpoints by AutoBackend.
+Apply the `[GenericController]` attribute to your models to have AutoBackend generate HTTP API endpoints.
> ❗**Disclaimer**
>
@@ -223,7 +216,7 @@ Use the `[GenericController]` attribute on models to generate HTTP API endpoints
### Response Container and Endpoints
-The current and only API version is v1. API v1 supports JSON and uses the following response container format:
+The current API version is v1. API v1 uses JSON and follows the following response container format:
```
{
@@ -235,7 +228,7 @@ The current and only API version is v1. API v1 supports JSON and uses the follow
}
```
-For detailed information on generated endpoints, access `/swagger`.
+For detailed information on generated endpoints, visit `/swagger`.
### Endpoints
@@ -276,15 +269,14 @@ public class User
## GraphQL
-Use the `[GenericGqlQuery]` and `[GenericGqlMutation]` attributes on models to generate GraphQL **queries** and *
-*mutations**, respectively.
+Apply the `[GenericGqlQuery]` and `[GenericGqlMutation]` attributes to your models to generate GraphQL **queries** and **mutations**, respectively.
> ❗**Disclaimer**
>
> The `[GenericGqlQuery]` and `[GenericGqlMutation]` attributes are compatible only with models also marked
> with `[GenericEntity]`.
-For detailed information on generated queries and mutations, access `/graphql`.
+For detailed information on generated queries and mutations, visit `/graphql`.
### Queries
@@ -347,14 +339,12 @@ public class User
## Modeling
-AutoBackend.SDK generates request and response models for any entity with configured HTTP API or GraphQL generation.
-These models include all original entity properties, unless specific properties are explicitly included in request or
-response models, in which case non-specified properties will be omitted.
+AutoBackend.SDK automatically generates request and response models for any entity with HTTP API or GraphQL generation configured.
+By default, these models include all original entity properties. However, if you explicitly specify properties using the `[GenericRequest]` or `[GenericResponse]` attributes, only those specified properties will be included, and all others will be omitted.
### Request Models
-The `[GenericRequest]` attribute defines which properties are permitted for reflection from the request model to the
-entity.
+The `[GenericRequest]` attribute specifies which properties can be mapped from the request model to the entity.
#### Code Samples
@@ -382,8 +372,7 @@ public class Transaction
### Response Models
-The `[GenericResponse]` attribute defines which properties are permitted for reflection from the response model to the
-entity.
+The `[GenericResponse]` attribute specifies which properties can be mapped from the entity to the response model.
#### Code Samples
@@ -408,6 +397,119 @@ public class Transaction
}
```
+## Authorization
+
+AutoBackend.SDK supports permission-based authorization using JWT tokens. You can control access to CRUD operations at both entity and property levels.
+
+### Configuration
+
+Configure JWT settings in your `appsettings.json`:
+
+```json
+"Jwt": {
+ "PublicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
+ "ValidIssuer": "AutoBackendServer",
+ "ValidAudience": "AutoBackendUser"
+}
+```
+
+- `PublicKey`: RSA public key in PEM format (required) - used to verify JWT token signatures
+- `ValidIssuer`: Optional issuer validation
+- `ValidAudience`: Optional audience validation
+
+### Permission Attributes
+
+Use permission attributes on entity classes to require authorization for specific operations:
+
+- `[GenericCreatePermission]` - Requires permission to create entities
+- `[GenericReadPermission]` - Requires permission to read entities
+- `[GenericUpdatePermission]` - Requires permission to update entities
+- `[GenericDeletePermission]` - Requires permission to delete entities
+
+#### Code Samples
+
+```csharp
+[GenericEntity(
+ nameof(Id)
+)]
+[GenericController]
+[GenericGqlQuery]
+[GenericGqlMutation]
+[GenericCreatePermission]
+[GenericReadPermission]
+[GenericUpdatePermission]
+[GenericDeletePermission]
+public class Budget
+{
+ public Guid Id { get; set; }
+
+ // ...
+}
+```
+
+### Property-Level Permissions
+
+> ⚠️ **Note**: Property-level permissions are currently only processed for **Update** operations.
+
+You can apply permission attributes to individual properties for fine-grained access control during update operations:
+
+```csharp
+[GenericEntity(
+ nameof(Id)
+)]
+[GenericController]
+[GenericGqlMutation]
+[GenericUpdatePermission]
+public class Transaction
+{
+ public Guid Id { get; set; }
+
+ [GenericUpdatePermission]
+ public string? SecretKey { get; set; }
+
+ // ...
+}
+```
+
+When updating an entity, AutoBackend will check property-level permissions only for properties that are actually being modified. This allows you to restrict access to sensitive fields while allowing updates to other properties.
+
+### JWT Token Format
+
+JWT tokens must be sent in the `Authorization` header with the `Bearer` prefix:
+
+```
+Authorization: Bearer
+```
+
+The JWT token must contain claims with:
+- **Type**: `permissions`
+- **Value**: Permission strings in the format `{EntityName}{PermissionType}` or `{EntityName}{PropertyName}{PermissionType}`
+
+#### Permission Name Format
+
+- **Entity permissions**: `{EntityName}{PermissionType}`
+ - Examples: `BudgetCreate`, `BudgetRead`, `BudgetUpdate`, `BudgetDelete`
+- **Property permissions**: `{EntityName}{PropertyName}{PermissionType}` (only for Update operations)
+ - Examples: `TransactionSecretKeyUpdate`
+
+#### Example JWT Claims
+
+```json
+{
+ "permissions": [
+ "BudgetCreate",
+ "BudgetRead",
+ "BudgetUpdate",
+ "BudgetDelete",
+ "TransactionSecretKeyUpdate"
+ ]
+}
+```
+
+### Error Handling
+
+When a request lacks required permissions, AutoBackend returns an `Unauthorized` error with details about missing permissions.
+
## Filtering
AutoBackend.SDK generates filter models for any entity with configured HTTP API or GraphQL generation. By default, these
@@ -416,15 +518,14 @@ the `[GenericFilter]` attribute.
### Defaults
-By default, two filter parameters are always available for any GET request (returning a list of entities) or GraphQL
-queries, to manage pagination:
+By default, two filter parameters are always available for any GET request (returning a list of entities) or GraphQL queries to manage pagination:
- `skipCount`: number
- `takeCount`: number
-### Generic
+### Custom Filtering
-The `[GenericFilter]` attribute designates a model property as filterable in the generated entity.
+The `[GenericFilter]` attribute marks a model property as filterable in the generated filter model.
```csharp
[GenericEntity(
@@ -448,11 +549,17 @@ public class Budget
}
```
-Consequently, AutoBackend will construct a filter model with parameters that can be utilized in API endpoints
-like `/api/v1/` or `/api/v1//count`, and in GraphQL queries.
+AutoBackend will construct a filter model with parameters that can be used in API endpoints like `/api/v1/` or `/api/v1//count`, as well as in GraphQL queries.
-- API filter parameter names are generated following the pattern: `.`.
-- GraphQL queries will have filtering models with condition properties generated.
+- API filter parameter names follow the pattern: `.`
+- GraphQL queries will have filtering models with condition properties generated
-The following conditions are supported:
-`equal`, `notEqual`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`, `in`, `isNull`, `equal`.
\ No newline at end of file
+The following filter conditions are supported:
+- `equal`
+- `notEqual`
+- `greaterThan`
+- `greaterThanOrEqual`
+- `lessThan`
+- `lessThanOrEqual`
+- `in`
+- `isNull`
\ No newline at end of file
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 8ac041e..0c65667 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -2,31 +2,39 @@
#### Enhancements:
-- Released version v0.1.9 with a comprehensive range of updates.
-- Implemented full filter support for GraphQL queries and mutations.
-- Introduced generic request and response models with support for primitive mapping.
-- Conducted refactoring of directories, mappers, and overall codebase for optimization purposes.
-- Updated issue templates, code of conduct, contribution, and security guidelines.
-- Added a new CODE_OF_CONDUCT.md document.
-- Enhanced the example project and detailed documentation within README.md.
-- Refined GitHub Actions and workflow processes.
+- Released version v0.1.10 with major infrastructure updates.
+- Added JWT authorization support with configurable public key validation.
+- Refactored AutoBackendHost configuration by consolidating StartupBoilerplate logic inline.
+- Enhanced sample project with PostgreSQL as primary database provider.
+- Updated README.md with comprehensive authorization documentation.
#### Package Updates:
-- Upgraded Microsoft.EntityFrameworkCore packages from 7.0.3 to 8.0.4 incrementally.
-- Updated Npgsql.EntityFrameworkCore.PostgreSQL from version 7.0.3 to 8.0.2.
-- Incremented NSwag.AspNetCore from 13.18.2 to 14.0.7.
-- Elevated coverlet.collector version from 3.2.0 to 6.0.2.
-- Updated tj-actions/changed-files from version 35 to 41.
-- Target framework advanced to .NET 8.0, with associated package versions updated accordingly.
-
-#### Bug Fixes:
-
-- Addressed and corrected mapper errors and instituted mapper caching enhancements.
-- Resolved minor refactoring issues and remedied warning notifications.
+- Target framework advanced to .NET 10.0 with SDK version 10.0.0.
+- Upgraded Microsoft.EntityFrameworkCore packages from 8.0.4 to 10.0.1.
+- Updated Npgsql.EntityFrameworkCore.PostgreSQL from version 8.0.2 to 10.0.0.
+- Upgraded HotChocolate.AspNetCore from 13.9.0 to 15.1.11.
+- Updated HotChocolate.Data.EntityFramework from 13.9.0 to 15.1.11.
+- Incremented NSwag.AspNetCore from 14.0.7 to 14.6.3.
+- Upgraded Microsoft.NET.Test.Sdk from 17.9.0 to 18.0.1.
+- Updated MSTest.TestAdapter from 3.3.1 to 4.0.2.
+- Updated MSTest.TestFramework from 3.3.1 to 4.0.2.
+- Elevated coverlet.collector version from 6.0.2 to 6.0.4.
+- Removed Flurl.Http dependency.
+
+#### Infrastructure Updates:
+
+- Updated Dockerfile base images to .NET 10.0.
+- Enhanced GitHub Actions workflows with latest versions.
+- Upgraded actions/checkout from v4 to v6.
+- Updated actions/setup-dotnet from v4 to v5.
+- Incremented tj-actions/changed-files from v41 to v47.
+- Upgraded actions/github-script from v7 to v8.
+- Enhanced Dependabot configuration with GitHub Actions support.
+- Added parallel test execution configuration.
#### Documentation and Organization:
-- Finalized and refined README.md documentation.
-- Systematized solution documents and rectified the location of the Contributor Covenant Code of Conduct.
-- Edited [sample project](src/samples/Sample)
\ No newline at end of file
+- Expanded README.md with authorization section and improved descriptions.
+- Updated sample project configuration with JWT settings.
+- Enhanced PostgreSQL connection string with error detail inclusion.
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Attributes/GenericCreatePermissionAttribute.cs b/src/lib/AutoBackend.Sdk/Attributes/GenericCreatePermissionAttribute.cs
new file mode 100644
index 0000000..6da10e5
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Attributes/GenericCreatePermissionAttribute.cs
@@ -0,0 +1,4 @@
+namespace AutoBackend.Sdk.Attributes;
+
+[AttributeUsage(AttributeTargets.Class)]
+public sealed class GenericCreatePermissionAttribute : Attribute;
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Attributes/GenericDeletePermissionAttribute.cs b/src/lib/AutoBackend.Sdk/Attributes/GenericDeletePermissionAttribute.cs
new file mode 100644
index 0000000..c9256c5
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Attributes/GenericDeletePermissionAttribute.cs
@@ -0,0 +1,4 @@
+namespace AutoBackend.Sdk.Attributes;
+
+[AttributeUsage(AttributeTargets.Class)]
+public sealed class GenericDeletePermissionAttribute : Attribute;
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Attributes/GenericReadPermissionAttribute.cs b/src/lib/AutoBackend.Sdk/Attributes/GenericReadPermissionAttribute.cs
new file mode 100644
index 0000000..cf61e5e
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Attributes/GenericReadPermissionAttribute.cs
@@ -0,0 +1,4 @@
+namespace AutoBackend.Sdk.Attributes;
+
+[AttributeUsage(AttributeTargets.Class)]
+public sealed class GenericReadPermissionAttribute : Attribute;
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Attributes/GenericUpdatePermissionAttribute.cs b/src/lib/AutoBackend.Sdk/Attributes/GenericUpdatePermissionAttribute.cs
new file mode 100644
index 0000000..44a981b
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Attributes/GenericUpdatePermissionAttribute.cs
@@ -0,0 +1,4 @@
+namespace AutoBackend.Sdk.Attributes;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
+public sealed class GenericUpdatePermissionAttribute : Attribute;
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Configuration/JwtConfiguration.cs b/src/lib/AutoBackend.Sdk/Configuration/JwtConfiguration.cs
new file mode 100644
index 0000000..a92cac3
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Configuration/JwtConfiguration.cs
@@ -0,0 +1,11 @@
+namespace AutoBackend.Sdk.Configuration;
+
+// ReSharper disable once ClassNeverInstantiated.Global
+internal sealed record JwtConfiguration
+{
+ public string PublicKey { get; init; } = null!;
+
+ public string? ValidIssuer { get; init; }
+
+ public string? ValidAudience { get; init; }
+}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Constants.cs b/src/lib/AutoBackend.Sdk/Constants.cs
index 88d325b..e4addc2 100644
--- a/src/lib/AutoBackend.Sdk/Constants.cs
+++ b/src/lib/AutoBackend.Sdk/Constants.cs
@@ -2,15 +2,10 @@ namespace AutoBackend.Sdk;
internal static class Constants
{
- #region Services
-
- internal const string ClusterDiscoveryServiceUrl = "/__discovery";
-
- #endregion
-
#region Headers
- internal const string XForwardedForHeaderName = "X-Forwarded-For";
+ internal const string AuthorizationHeaderName = "Authorization";
+ internal const string AuthorizationBearerPrefix = "Bearer ";
#endregion
@@ -59,6 +54,11 @@ internal static class Constants
internal const string PropertySetterName = "set_{0}";
internal const string GenericTypeBeautifulName = "{0}<{1}>";
+
+ internal const string JwtConfigurationSectionName = "Jwt";
+ internal const string GenericEntityPermissionName = "{0}{1}";
+ internal const string GenericPropertyPermissionName = "{0}{1}{2}";
+ public const string GenericPermissionsClaimType = "permissions";
#endregion
@@ -92,9 +92,6 @@ internal static class Constants
internal const string UnableToFindAMethodWithParametersForTheType =
"Unable to find a method {0} with {1} parameters for the type {2}.";
- internal const string UnableToFindAMethodWithParametersAndGenericArgumentsForTheType =
- "Unable to find a method {0} with {1} parameters and {2} generic arguments for the type {3}.";
-
internal const string TheEntityTypeKeySetDoesNotMatchTheGivenKeys =
"The entity type key set doesn't match the given keys.";
@@ -124,6 +121,9 @@ internal static class Constants
internal const string UnableToBuildAGenericGraphQlMutationForTypeThePropertyHasNotBeenFound =
"Unable to build a generic GraphQL mutation for type {0}. The property {1} has not been found.";
+
+ internal const string Unauthorized = "Unauthorized";
+ internal const string UnauthorizedMissingPermissions = "Unauthorized. Missing permissions: [{0}]";
#endregion
@@ -137,17 +137,7 @@ internal static class Constants
#region Environment
- internal const string HostEnvironmentVariable = "HOST";
internal const string PortEnvironmentVariable = "PORT";
#endregion
-
- #region Logs
-
- internal const string ClusterDiscoveryRequestLogName = "discovery request";
-
- internal const string ClusterDiscoveryRequestLogMessage =
- "Discover at (node: ({NodeId}, {NodeCreatedUtc}, {NodeLastRequestToUtc}, {NodeLastRequestFromUtc}, {NodeRequestTimeMs})) with params (node: ({RemoteNodeId}, {RemoteNodeCreated}, {RemoteNodeLastRequestTo}, {RemoteNodeLastRequestFrom}, {RemoteNodeRequestTimeMs}))";
-
- #endregion
}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Controllers/GenericController.cs b/src/lib/AutoBackend.Sdk/Controllers/GenericController.cs
index 4781d3a..d4eee0f 100644
--- a/src/lib/AutoBackend.Sdk/Controllers/GenericController.cs
+++ b/src/lib/AutoBackend.Sdk/Controllers/GenericController.cs
@@ -35,28 +35,27 @@ private void SetupExceptionHandler()
{
HttpContext
.RequestServices
- .GetRequiredService().SetCurrentHandler(
+ .GetRequiredService().SetCurrentApiHandler(
HttpContext,
HandleExceptionStatusCodeAsync,
HandleExceptionResponseAsync);
}
- private Task HandleExceptionStatusCodeAsync(Exception exception)
+ private static int HandleExceptionStatusCodeAsync(Exception exception)
{
- return Task.FromResult(exception.ToApiException().StatusCode);
+ return exception.ToApiException().StatusCode;
}
- private Task HandleExceptionResponseAsync(Exception exception)
+ private GenericControllerResponse HandleExceptionResponseAsync(Exception exception)
{
var statusCode = exception.ToApiException().StatusCode;
- return Task.FromResult(
- new GenericControllerResponse(
- false,
- statusCode,
- statusCode == StatusCodes.Status500InternalServerError
- ? Constants.AnUnexpectedInternalServerErrorHasHappened
- : exception.Message)
- .WithRequestTime(HttpContext));
+ return new GenericControllerResponse(
+ false,
+ statusCode,
+ statusCode == StatusCodes.Status500InternalServerError
+ ? Constants.AnUnexpectedInternalServerErrorHasHappened
+ : exception.Message)
+ .WithRequestTime(HttpContext);
}
}
@@ -68,7 +67,7 @@ internal abstract class GenericController<
IGenericResponseMapper genericResponseMapper,
IGenericRepository genericRepository,
ICancellationTokenProvider cancellationTokenProvider) : GenericController
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
{
diff --git a/src/lib/AutoBackend.Sdk/Data/Mappers/GenericResponseMapper.cs b/src/lib/AutoBackend.Sdk/Data/Mappers/GenericResponseMapper.cs
index f0cc1ee..a68542d 100644
--- a/src/lib/AutoBackend.Sdk/Data/Mappers/GenericResponseMapper.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Mappers/GenericResponseMapper.cs
@@ -9,7 +9,7 @@ namespace AutoBackend.Sdk.Data.Mappers;
internal class GenericResponseMapper(IMapperExpressionsCache cache) : IGenericResponseMapper
{
public TResponse ToModel(TEntity entity)
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
{
var func = cache.GetOrAddAndCompile(MapExpr());
@@ -22,14 +22,14 @@ IEnumerable IGenericResponseMapper.ToModel(IEnume
}
public TResponse? ToModelNullable(TEntity? entity)
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
{
return entity is null ? null : ToModel(entity);
}
public ICollection ToModelCollectionNullable(ICollection? entities)
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
{
return entities?.Select(entity =>
@@ -37,12 +37,11 @@ public ICollection ToModelCollectionNullable(ICol
?? throw new InconsistentDataException())
.ToArray()
??
- [
- ];
+ [];
}
private Expression> MapExpr()
- where TEntity : class
+ where TEntity : class, new()
where TModel : class, IGenericResponse, new()
{
var parameter = Expression.Parameter(typeof(TEntity));
diff --git a/src/lib/AutoBackend.Sdk/Data/Mappers/IGenericResponseMapper.cs b/src/lib/AutoBackend.Sdk/Data/Mappers/IGenericResponseMapper.cs
index b72c1ad..9fbafbd 100644
--- a/src/lib/AutoBackend.Sdk/Data/Mappers/IGenericResponseMapper.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Mappers/IGenericResponseMapper.cs
@@ -5,10 +5,10 @@ namespace AutoBackend.Sdk.Data.Mappers;
internal interface IGenericResponseMapper
{
TResponse ToModel(TEntity entity)
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new();
IEnumerable ToModel(IEnumerable entities)
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new();
}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepository.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepository.cs
index 54e11ec..dbc2f80 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepository.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepository.cs
@@ -1,18 +1,23 @@
using AutoBackend.Sdk.Data.Storage;
+using AutoBackend.Sdk.Enums;
using AutoBackend.Sdk.Exceptions.Data;
using AutoBackend.Sdk.Exceptions.Reflection;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
using Microsoft.EntityFrameworkCore;
namespace AutoBackend.Sdk.Data.Repositories;
-internal class GenericRepository(IGenericStorage genericStorage)
+internal class GenericRepository(
+ IPermissionValidator permissionValidator,
+ IGenericStorage genericStorage)
: IGenericRepository
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
{
public Task GetAllAsync(TFilter? filter, CancellationToken cancellationToken)
{
+ permissionValidator.Validate(PermissionType.Read);
var query = genericStorage.GetQuery(filter);
if (filter?.SkipCount is { } skipCountValue) query = query.Skip(skipCountValue);
if (filter?.TakeCount is { } takeCountValue) query = query.Take(takeCountValue);
@@ -21,6 +26,7 @@ public Task GetAllAsync(TFilter? filter, CancellationToken cancellati
public Task GetCountAsync(TFilter? filter, CancellationToken cancellationToken)
{
+ permissionValidator.Validate(PermissionType.Read);
return genericStorage.GetQuery(filter).LongCountAsync(cancellationToken);
}
@@ -28,6 +34,7 @@ public Task GetCountAsync(TFilter? filter, CancellationToken cancellationT
CancellationToken cancellationToken,
params object[] keys)
{
+ permissionValidator.Validate(PermissionType.Read);
return await genericStorage.FindAsync(keys, cancellationToken);
}
@@ -36,6 +43,7 @@ protected async Task CreateInternalAsync(
CancellationToken cancellationToken,
params object[]? keys)
{
+ permissionValidator.Validate(PermissionType.Create);
if (keys?.Any() ?? false)
{
ValidateEntityKeys(entity, keys);
@@ -57,6 +65,7 @@ protected async Task UpdateInternalAsync(
CancellationToken cancellationToken,
params object[] keys)
{
+ permissionValidator.Validate(PermissionType.Update);
ValidateEntityKeys(entity, keys);
var current = await genericStorage.FindAsync(keys, cancellationToken);
if (current is null)
@@ -65,6 +74,7 @@ protected async Task UpdateInternalAsync(
Constants.AnEntityWithTheGivenKeyDoesNotExist,
string.Join(", ", keys.Select(key => key.ToString()))));
+ var affectedProperties = new HashSet();
foreach (var entryProperty in genericStorage.Entry(current).Properties)
{
var entityProperty = typeof(TEntity).GetProperty(entryProperty.Metadata.Name);
@@ -76,8 +86,10 @@ protected async Task UpdateInternalAsync(
typeof(TEntity).Name));
var newValue = entityProperty.GetValue(entity);
entityProperty.SetValue(current, newValue);
+ if (!Equals(entryProperty.OriginalValue, entryProperty.CurrentValue)) affectedProperties.Add(entryProperty.Metadata.Name);
}
+ permissionValidator.Validate(PermissionType.Update, affectedProperties);
genericStorage.Update(current);
await genericStorage.SaveChangesAsync(cancellationToken);
return entity;
@@ -87,6 +99,7 @@ protected async Task DeleteInternalAsync(
CancellationToken cancellationToken,
params object[] keys)
{
+ permissionValidator.Validate(PermissionType.Delete);
var current = await genericStorage.FindAsync(keys, cancellationToken);
if (current is null)
throw new NotFoundDataException(
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.3.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.3.cs
index 3217d17..2aac387 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.3.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.3.cs
@@ -1,5 +1,6 @@
using AutoBackend.Sdk.Data.Storage;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
namespace AutoBackend.Sdk.Data.Repositories;
@@ -9,17 +10,17 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey1,
TKey2,
TKey3
->(IGenericStorage genericStorage) : GenericRepository<
+>(IPermissionValidator permissionValidator, IGenericStorage genericStorage) : GenericRepository<
TEntity,
TFilter
->(genericStorage), IGenericRepositoryWithComplexKey<
+>(permissionValidator, genericStorage), IGenericRepositoryWithComplexKey<
TEntity,
TFilter,
TKey1,
TKey2,
TKey3
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.4.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.4.cs
index 8384159..9e1e240 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.4.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.4.cs
@@ -1,5 +1,6 @@
using AutoBackend.Sdk.Data.Storage;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
namespace AutoBackend.Sdk.Data.Repositories;
@@ -10,10 +11,10 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey2,
TKey3,
TKey4
->(IGenericStorage genericStorage) : GenericRepository<
+>(IPermissionValidator permissionValidator, IGenericStorage genericStorage) : GenericRepository<
TEntity,
TFilter
->(genericStorage), IGenericRepositoryWithComplexKey<
+>(permissionValidator, genericStorage), IGenericRepositoryWithComplexKey<
TEntity,
TFilter,
TKey1,
@@ -21,7 +22,7 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey3,
TKey4
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.5.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.5.cs
index aabffbf..c2348fd 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.5.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.5.cs
@@ -1,5 +1,6 @@
using AutoBackend.Sdk.Data.Storage;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
namespace AutoBackend.Sdk.Data.Repositories;
@@ -11,10 +12,10 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey3,
TKey4,
TKey5
->(IGenericStorage genericStorage) : GenericRepository<
+>(IPermissionValidator permissionValidator, IGenericStorage genericStorage) : GenericRepository<
TEntity,
TFilter
->(genericStorage), IGenericRepositoryWithComplexKey<
+>(permissionValidator, genericStorage), IGenericRepositoryWithComplexKey<
TEntity,
TFilter,
TKey1,
@@ -23,7 +24,7 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey4,
TKey5
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.6.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.6.cs
index 564934c..e26954c 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.6.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.6.cs
@@ -1,5 +1,6 @@
using AutoBackend.Sdk.Data.Storage;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
namespace AutoBackend.Sdk.Data.Repositories;
@@ -12,10 +13,10 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey4,
TKey5,
TKey6
->(IGenericStorage genericStorage) : GenericRepository<
+>(IPermissionValidator permissionValidator, IGenericStorage genericStorage) : GenericRepository<
TEntity,
TFilter
->(genericStorage), IGenericRepositoryWithComplexKey<
+>(permissionValidator, genericStorage), IGenericRepositoryWithComplexKey<
TEntity,
TFilter,
TKey1,
@@ -25,7 +26,7 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey5,
TKey6
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.7.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.7.cs
index cabf8ba..4edeeee 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.7.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.7.cs
@@ -1,5 +1,6 @@
using AutoBackend.Sdk.Data.Storage;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
namespace AutoBackend.Sdk.Data.Repositories;
@@ -13,10 +14,10 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey5,
TKey6,
TKey7
->(IGenericStorage genericStorage) : GenericRepository<
+>(IPermissionValidator permissionValidator, IGenericStorage genericStorage) : GenericRepository<
TEntity,
TFilter
->(genericStorage), IGenericRepositoryWithComplexKey<
+>(permissionValidator, genericStorage), IGenericRepositoryWithComplexKey<
TEntity,
TFilter,
TKey1,
@@ -27,7 +28,7 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey6,
TKey7
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.8.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.8.cs
index 1fadc96..3c8d97b 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.8.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.8.cs
@@ -1,5 +1,6 @@
using AutoBackend.Sdk.Data.Storage;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
namespace AutoBackend.Sdk.Data.Repositories;
@@ -14,10 +15,10 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey6,
TKey7,
TKey8
->(IGenericStorage genericStorage) : GenericRepository<
+>(IPermissionValidator permissionValidator, IGenericStorage genericStorage) : GenericRepository<
TEntity,
TFilter
->(genericStorage), IGenericRepositoryWithComplexKey<
+>(permissionValidator, genericStorage), IGenericRepositoryWithComplexKey<
TEntity,
TFilter,
TKey1,
@@ -29,7 +30,7 @@ internal sealed class GenericRepositoryWithComplexKey<
TKey7,
TKey8
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.cs
index ae7d3ec..0f58f93 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithComplexKey.cs
@@ -1,5 +1,6 @@
using AutoBackend.Sdk.Data.Storage;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
namespace AutoBackend.Sdk.Data.Repositories;
@@ -8,16 +9,16 @@ internal sealed class GenericRepositoryWithComplexKey<
TFilter,
TKey1,
TKey2
->(IGenericStorage genericStorage) : GenericRepository<
+>(IPermissionValidator permissionValidator, IGenericStorage genericStorage) : GenericRepository<
TEntity,
TFilter
->(genericStorage), IGenericRepositoryWithComplexKey<
+>(permissionValidator, genericStorage), IGenericRepositoryWithComplexKey<
TEntity,
TFilter,
TKey1,
TKey2
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithNoKey.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithNoKey.cs
index 5ad9b45..5436c7f 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithNoKey.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithNoKey.cs
@@ -1,19 +1,20 @@
using AutoBackend.Sdk.Data.Storage;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
namespace AutoBackend.Sdk.Data.Repositories;
internal sealed class GenericRepositoryWithNoKey<
TEntity,
TFilter
->(IGenericStorage genericStorage) : GenericRepository<
+>(IPermissionValidator permissionValidator, IGenericStorage genericStorage) : GenericRepository<
TEntity,
TFilter
->(genericStorage), IGenericRepositoryWithNoKey<
+>(permissionValidator, genericStorage), IGenericRepositoryWithNoKey<
TEntity,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
{
public Task CreateAsync(TEntity entity, CancellationToken cancellationToken)
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithPrimaryKey.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithPrimaryKey.cs
index 28aa878..175dbc0 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithPrimaryKey.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/GenericRepositoryWithPrimaryKey.cs
@@ -1,5 +1,6 @@
using AutoBackend.Sdk.Data.Storage;
using AutoBackend.Sdk.Filters;
+using AutoBackend.Sdk.Services.PermissionValidator;
namespace AutoBackend.Sdk.Data.Repositories;
@@ -7,15 +8,15 @@ internal sealed class GenericRepositoryWithPrimaryKey<
TEntity,
TFilter,
TKey
->(IGenericStorage genericStorage) : GenericRepository<
+>(IPermissionValidator permissionValidator, IGenericStorage genericStorage) : GenericRepository<
TEntity,
TFilter
->(genericStorage), IGenericRepositoryWithPrimaryKey<
+>(permissionValidator, genericStorage), IGenericRepositoryWithPrimaryKey<
TEntity,
TFilter,
TKey
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey : notnull
{
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepository.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepository.cs
index 9ad0996..fd7ce1a 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepository.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepository.cs
@@ -3,7 +3,7 @@
namespace AutoBackend.Sdk.Data.Repositories;
internal interface IGenericRepository
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
{
Task GetAllAsync(TFilter? filter, CancellationToken cancellationToken);
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.3.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.3.cs
index 31d8e79..4dcb633 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.3.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.3.cs
@@ -9,7 +9,7 @@ internal interface IGenericRepositoryWithComplexKey<
in TKey2,
in TKey3
> : IGenericRepository
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.4.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.4.cs
index 4846fa0..c914f2d 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.4.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.4.cs
@@ -10,7 +10,7 @@ internal interface IGenericRepositoryWithComplexKey<
in TKey3,
in TKey4
> : IGenericRepository
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.5.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.5.cs
index 4020a85..2e450fc 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.5.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.5.cs
@@ -11,7 +11,7 @@ internal interface IGenericRepositoryWithComplexKey<
in TKey4,
in TKey5
> : IGenericRepository
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.6.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.6.cs
index 479b832..484d717 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.6.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.6.cs
@@ -12,7 +12,7 @@ internal interface IGenericRepositoryWithComplexKey<
in TKey5,
in TKey6
> : IGenericRepository
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.7.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.7.cs
index 64ba195..58bcb90 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.7.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.7.cs
@@ -13,7 +13,7 @@ internal interface IGenericRepositoryWithComplexKey<
in TKey6,
in TKey7
> : IGenericRepository
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.8.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.8.cs
index bff21d9..5db7280 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.8.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.8.cs
@@ -14,7 +14,7 @@ internal interface IGenericRepositoryWithComplexKey<
in TKey7,
in TKey8
> : IGenericRepository
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.cs
index d0a6920..8bffe08 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithComplexKey.cs
@@ -8,7 +8,7 @@ internal interface IGenericRepositoryWithComplexKey<
in TKey1,
in TKey2
> : IGenericRepository
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
where TKey2 : notnull
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithNoKey.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithNoKey.cs
index 79aca20..43cc168 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithNoKey.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithNoKey.cs
@@ -9,7 +9,7 @@ in TFilter
TEntity,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
{
Task CreateAsync(TEntity entity, CancellationToken cancellationToken);
diff --git a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithPrimaryKey.cs b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithPrimaryKey.cs
index 1b38181..9b20058 100644
--- a/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithPrimaryKey.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Repositories/IGenericRepositoryWithPrimaryKey.cs
@@ -10,7 +10,7 @@ in TKey
TEntity,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
where TKey : notnull
{
diff --git a/src/lib/AutoBackend.Sdk/Data/Storage/GenericStorage.cs b/src/lib/AutoBackend.Sdk/Data/Storage/GenericStorage.cs
index 55eba1d..2c08022 100644
--- a/src/lib/AutoBackend.Sdk/Data/Storage/GenericStorage.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Storage/GenericStorage.cs
@@ -8,7 +8,7 @@
namespace AutoBackend.Sdk.Data.Storage;
internal sealed class GenericStorage(GenericDbContext db) : IGenericStorage
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
{
private DbSet Set => db.Set();
diff --git a/src/lib/AutoBackend.Sdk/Data/Storage/IGenericStorage.cs b/src/lib/AutoBackend.Sdk/Data/Storage/IGenericStorage.cs
index 9e067bf..df69538 100644
--- a/src/lib/AutoBackend.Sdk/Data/Storage/IGenericStorage.cs
+++ b/src/lib/AutoBackend.Sdk/Data/Storage/IGenericStorage.cs
@@ -4,7 +4,7 @@
namespace AutoBackend.Sdk.Data.Storage;
internal interface IGenericStorage
- where TEntity : class
+ where TEntity : class, new()
where TFilter : class, IGenericFilter
{
ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken);
diff --git a/src/lib/AutoBackend.Sdk/Enums/PermissionType.cs b/src/lib/AutoBackend.Sdk/Enums/PermissionType.cs
new file mode 100644
index 0000000..feaa806
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Enums/PermissionType.cs
@@ -0,0 +1,10 @@
+namespace AutoBackend.Sdk.Enums;
+
+[Flags]
+internal enum PermissionType
+{
+ Create = 1,
+ Read = 1 << 1,
+ Update = 1 << 2,
+ Delete = 1 << 3
+}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Exceptions/Api/UnauthorizedApiException.cs b/src/lib/AutoBackend.Sdk/Exceptions/Api/UnauthorizedApiException.cs
new file mode 100644
index 0000000..a2142aa
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Exceptions/Api/UnauthorizedApiException.cs
@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Http;
+
+namespace AutoBackend.Sdk.Exceptions.Api;
+
+internal sealed class UnauthorizedApiException : ApiException
+{
+ internal UnauthorizedApiException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ internal override int StatusCode => StatusCodes.Status401Unauthorized;
+}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Exceptions/Data/UnauthorizedAccessDataException.cs b/src/lib/AutoBackend.Sdk/Exceptions/Data/UnauthorizedAccessDataException.cs
new file mode 100644
index 0000000..bab9c21
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Exceptions/Data/UnauthorizedAccessDataException.cs
@@ -0,0 +1,11 @@
+namespace AutoBackend.Sdk.Exceptions.Data;
+
+internal class UnauthorizedAccessDataException : DataException
+{
+ internal UnauthorizedAccessDataException(string message) : base(message)
+ {
+ }
+ internal UnauthorizedAccessDataException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Extensions/ExceptionExtensions.cs b/src/lib/AutoBackend.Sdk/Extensions/ExceptionExtensions.cs
index be5872d..28bb153 100644
--- a/src/lib/AutoBackend.Sdk/Extensions/ExceptionExtensions.cs
+++ b/src/lib/AutoBackend.Sdk/Extensions/ExceptionExtensions.cs
@@ -26,6 +26,9 @@ internal ApiException ToApiException()
NotFoundDataException => new NotFoundApiException(
Constants.NoDataHasBeenFound,
exception),
+ UnauthorizedAccessDataException => new UnauthorizedApiException(
+ exception.Message,
+ exception),
_ => fallbackValue
},
_ => fallbackValue
diff --git a/src/lib/AutoBackend.Sdk/Extensions/ServiceCollectionExtensions.cs b/src/lib/AutoBackend.Sdk/Extensions/ServiceCollectionExtensions.cs
index 00c7094..609384e 100644
--- a/src/lib/AutoBackend.Sdk/Extensions/ServiceCollectionExtensions.cs
+++ b/src/lib/AutoBackend.Sdk/Extensions/ServiceCollectionExtensions.cs
@@ -10,7 +10,9 @@
using AutoBackend.Sdk.NSwag;
using AutoBackend.Sdk.Services.CancellationTokenProvider;
using AutoBackend.Sdk.Services.DateTimeProvider;
+using AutoBackend.Sdk.Services.EntityMetadataProvider;
using AutoBackend.Sdk.Services.ExceptionHandler;
+using AutoBackend.Sdk.Services.PermissionValidator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -31,6 +33,7 @@ internal IServiceCollection AddAutoBackend(IConfiguration configuratio
.AddGenericSwagger(typeof(TProgram).Assembly.FullName!)
.AddGenericDbContext(configuration)
.AddGenericStorage()
+ .AddGenericPermissions(configuration)
.AddInternalServices();
}
@@ -206,12 +209,26 @@ private IServiceCollection AddGenericStorage()
return services;
}
+ private IServiceCollection AddGenericPermissions(IConfiguration configuration)
+ {
+ services.Configure(
+ configuration
+ .GetSection(Constants.JwtConfigurationSectionName));
+
+ services
+ .AddScoped();
+
+ return services;
+ }
+
private IServiceCollection AddInternalServices()
{
return services
.AddSingleton()
.AddSingleton()
- .AddScoped();
+ .AddSingleton()
+ .AddScoped()
+ .AddHttpContextAccessor();
}
}
}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQuery.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQuery.cs
index 411d0f8..b18ec08 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQuery.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQuery.cs
@@ -11,7 +11,7 @@ internal abstract class GenericGqlQuery<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
{
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.3.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.3.cs
index ac088c1..3a3d83e 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.3.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.3.cs
@@ -17,7 +17,7 @@ internal abstract class GenericGqlQueryWithComplexKey<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.4.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.4.cs
index 1ea8508..758f2d2 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.4.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.4.cs
@@ -18,7 +18,7 @@ internal abstract class GenericGqlQueryWithComplexKey<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.5.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.5.cs
index 5fdfea6..4b84640 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.5.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.5.cs
@@ -19,7 +19,7 @@ internal abstract class GenericGqlQueryWithComplexKey<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.6.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.6.cs
index 5ff861c..26edd6c 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.6.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.6.cs
@@ -20,7 +20,7 @@ internal abstract class GenericGqlQueryWithComplexKey<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.7.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.7.cs
index 460e311..a73cb48 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.7.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.7.cs
@@ -21,7 +21,7 @@ internal abstract class GenericGqlQueryWithComplexKey<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.8.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.8.cs
index 0d0f04f..752c1f8 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.8.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.8.cs
@@ -22,7 +22,7 @@ internal abstract class GenericGqlQueryWithComplexKey<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.cs
index a2e3ae4..2b12d24 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithComplexKey.cs
@@ -16,7 +16,7 @@ internal abstract class GenericGqlQueryWithComplexKey<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
where TKey1 : notnull
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithNoKey.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithNoKey.cs
index e4f3741..4034684 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithNoKey.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithNoKey.cs
@@ -12,6 +12,6 @@ internal abstract class GenericGqlQueryWithNoKey<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter;
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithPrimaryKey.cs b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithPrimaryKey.cs
index afe1675..1212a38 100644
--- a/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithPrimaryKey.cs
+++ b/src/lib/AutoBackend.Sdk/GraphQL/Queries/GenericGqlQueryWithPrimaryKey.cs
@@ -16,7 +16,7 @@ internal abstract class GenericGqlQueryWithPrimaryKey<
TResponse,
TFilter
>
- where TEntity : class
+ where TEntity : class, new()
where TResponse : class, IGenericResponse, new()
where TFilter : class, IGenericFilter
where TKey : notnull
diff --git a/src/lib/AutoBackend.Sdk/Middleware/ApiExceptionHandler/ApiExceptionHandlerMiddleware.cs b/src/lib/AutoBackend.Sdk/Middleware/ApiExceptionHandler/ApiExceptionHandlerMiddleware.cs
index 634d714..05790ab 100644
--- a/src/lib/AutoBackend.Sdk/Middleware/ApiExceptionHandler/ApiExceptionHandlerMiddleware.cs
+++ b/src/lib/AutoBackend.Sdk/Middleware/ApiExceptionHandler/ApiExceptionHandlerMiddleware.cs
@@ -12,9 +12,9 @@ public async Task Invoke(
IExceptionHandlerFactory exceptionHandlerFactory,
ILogger logger)
{
- exceptionHandlerFactory.SetCurrentHandler(
+ exceptionHandlerFactory.SetCurrentApiHandler(
httpContext,
- HandleExceptionStatusCodeDefaultAsync,
+ HandleExceptionStatusCodeDefault,
HandleExceptionResponseDefaultAsyncProvider(logger));
try
{
@@ -22,25 +22,25 @@ public async Task Invoke(
}
catch (Exception exception)
{
- await exceptionHandlerFactory.CurrentHandler!.HandleAsync(exception, CancellationToken.None);
+ await exceptionHandlerFactory.CurrentApiExceptionHandler!.HandleAsync(exception, CancellationToken.None);
}
}
- private Task HandleExceptionStatusCodeDefaultAsync(Exception exception)
+ private static int HandleExceptionStatusCodeDefault(Exception exception)
{
- return Task.FromResult(exception.ToApiException().StatusCode);
+ return exception.ToApiException().StatusCode;
}
- private static Func> HandleExceptionResponseDefaultAsyncProvider(ILogger logger)
+ private static Func HandleExceptionResponseDefaultAsyncProvider(ILogger logger)
{
return HandleExceptionResponseDefaultAsync;
- Task HandleExceptionResponseDefaultAsync(Exception exception)
+ string HandleExceptionResponseDefaultAsync(Exception exception)
{
logger.LogCritical(
exception,
Constants.AnUnexpectedInternalServerErrorHasHappenedOutOfTheControllerContext);
- return Task.FromResult(Constants.AnUnexpectedInternalServerErrorHasHappened);
+ return Constants.AnUnexpectedInternalServerErrorHasHappened;
}
}
}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/EntityMetadataProvider.cs b/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/EntityMetadataProvider.cs
new file mode 100644
index 0000000..b6156cc
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/EntityMetadataProvider.cs
@@ -0,0 +1,82 @@
+using System.Reflection;
+using AutoBackend.Sdk.Attributes;
+using AutoBackend.Sdk.Enums;
+
+namespace AutoBackend.Sdk.Services.EntityMetadataProvider;
+
+internal sealed class EntityMetadataProvider : IEntityMetadataProvider
+{
+ private readonly Dictionary _cache = new();
+
+ public GenericEntityMetadata GetMetadata() where TEntity : class, new()
+ {
+ var type = typeof(TEntity);
+ if (_cache.TryGetValue(type, out var metadata)) return metadata;
+ metadata = CreateMetadata(type);
+ _cache.Add(type, metadata);
+ return metadata;
+ }
+
+ private GenericEntityMetadata CreateMetadata(Type type)
+ {
+ var propertyInfos = type.GetProperties();
+ var properties = propertyInfos.Select(CreateMetadata).ToList();
+ var permissions = CreatePermissionType(type);
+ return new GenericEntityMetadata(
+ type.Name,
+ permissions,
+ properties);
+ }
+
+ private GenericPropertyMetadata CreateMetadata(PropertyInfo propertyInfo)
+ {
+ var permissions = CreatePermissionType(propertyInfo);
+ return new GenericPropertyMetadata(
+ propertyInfo.Name,
+ permissions);
+ }
+
+ private static PermissionType CreatePermissionType(Type type)
+ {
+ PermissionType permissionType = 0;
+ if (type.GetCustomAttribute() is not null)
+ {
+ permissionType |= PermissionType.Create;
+ }
+ if (type.GetCustomAttribute() is not null)
+ {
+ permissionType |= PermissionType.Read;
+ }
+ if (type.GetCustomAttribute() is not null)
+ {
+ permissionType |= PermissionType.Update;
+ }
+ if (type.GetCustomAttribute() is not null)
+ {
+ permissionType |= PermissionType.Delete;
+ }
+ return permissionType;
+ }
+
+ private static PermissionType CreatePermissionType(PropertyInfo propertyInfo)
+ {
+ PermissionType permissionType = 0;
+ if (propertyInfo.GetCustomAttribute() is not null)
+ {
+ permissionType |= PermissionType.Create;
+ }
+ if (propertyInfo.GetCustomAttribute() is not null)
+ {
+ permissionType |= PermissionType.Read;
+ }
+ if (propertyInfo.GetCustomAttribute() is not null)
+ {
+ permissionType |= PermissionType.Update;
+ }
+ if (propertyInfo.GetCustomAttribute() is not null)
+ {
+ permissionType |= PermissionType.Delete;
+ }
+ return permissionType;
+ }
+}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/GenericEntityMetadata.cs b/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/GenericEntityMetadata.cs
new file mode 100644
index 0000000..2bccfd2
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/GenericEntityMetadata.cs
@@ -0,0 +1,8 @@
+using AutoBackend.Sdk.Enums;
+
+namespace AutoBackend.Sdk.Services.EntityMetadataProvider;
+
+internal record GenericEntityMetadata(
+ string Name,
+ PermissionType Permissions,
+ IEnumerable Properties);
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/GenericPropertyMetadata.cs b/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/GenericPropertyMetadata.cs
new file mode 100644
index 0000000..807063a
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/GenericPropertyMetadata.cs
@@ -0,0 +1,7 @@
+using AutoBackend.Sdk.Enums;
+
+namespace AutoBackend.Sdk.Services.EntityMetadataProvider;
+
+internal record GenericPropertyMetadata(
+ string Name,
+ PermissionType Permissions);
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/IEntityMetadataProvider.cs b/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/IEntityMetadataProvider.cs
new file mode 100644
index 0000000..0f4aed7
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Services/EntityMetadataProvider/IEntityMetadataProvider.cs
@@ -0,0 +1,6 @@
+namespace AutoBackend.Sdk.Services.EntityMetadataProvider;
+
+internal interface IEntityMetadataProvider
+{
+ GenericEntityMetadata GetMetadata() where TEntity : class, new();
+}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/ExceptionHandlerFactory.cs b/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/ExceptionHandlerFactory.cs
index a4ca23f..f53e9a3 100644
--- a/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/ExceptionHandlerFactory.cs
+++ b/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/ExceptionHandlerFactory.cs
@@ -5,29 +5,29 @@ namespace AutoBackend.Sdk.Services.ExceptionHandler;
internal sealed class ExceptionHandlerFactory : IExceptionHandlerFactory
{
- public IExceptionHandler? CurrentHandler { get; private set; }
+ public IApiExceptionHandler? CurrentApiExceptionHandler { get; private set; }
- public void SetCurrentHandler(
+ public void SetCurrentApiHandler(
HttpContext httpContext,
- Func> handleExceptionStatusCodeAsync,
- Func> handleExceptionResponseAsync)
+ Func handleExceptionStatusCode,
+ Func handleExceptionResponse)
{
- CurrentHandler = new ExceptionHandler(
+ CurrentApiExceptionHandler = new ApiExceptionHandler(
httpContext,
- handleExceptionStatusCodeAsync,
- handleExceptionResponseAsync);
+ handleExceptionStatusCode,
+ handleExceptionResponse);
}
- private sealed class ExceptionHandler(
+ private sealed class ApiExceptionHandler(
HttpContext httpContext,
- Func> processStatusCode,
- Func> processResponse)
- : IExceptionHandler
+ Func processStatusCode,
+ Func processResponse)
+ : IApiExceptionHandler
{
public async Task HandleAsync(Exception exception, CancellationToken cancellationToken)
{
- var statusCode = await processStatusCode(exception);
- var response = await processResponse(exception);
+ var statusCode = processStatusCode(exception);
+ var response = processResponse(exception);
httpContext.Response.StatusCode = statusCode;
httpContext.Response.ContentType = MediaTypeNames.Application.Json;
await httpContext.Response.WriteAsJsonAsync(response, cancellationToken);
diff --git a/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IExceptionHandler.cs b/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IApiExceptionHandler.cs
similarity index 77%
rename from src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IExceptionHandler.cs
rename to src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IApiExceptionHandler.cs
index 6f6eb03..6f9641f 100644
--- a/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IExceptionHandler.cs
+++ b/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IApiExceptionHandler.cs
@@ -1,6 +1,6 @@
namespace AutoBackend.Sdk.Services.ExceptionHandler;
-internal interface IExceptionHandler
+internal interface IApiExceptionHandler
{
Task HandleAsync(Exception exception, CancellationToken cancellationToken);
}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IExceptionHandlerFactory.cs b/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IExceptionHandlerFactory.cs
index db1c2ea..40616ef 100644
--- a/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IExceptionHandlerFactory.cs
+++ b/src/lib/AutoBackend.Sdk/Services/ExceptionHandler/IExceptionHandlerFactory.cs
@@ -4,10 +4,10 @@ namespace AutoBackend.Sdk.Services.ExceptionHandler;
internal interface IExceptionHandlerFactory
{
- IExceptionHandler? CurrentHandler { get; }
-
- void SetCurrentHandler(
+ IApiExceptionHandler? CurrentApiExceptionHandler { get; }
+
+ void SetCurrentApiHandler(
HttpContext httpContext,
- Func> handleExceptionStatusCodeAsync,
- Func> handleExceptionResponseAsync);
+ Func handleExceptionStatusCode,
+ Func handleExceptionResponse);
}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Services/PermissionValidator/IPermissionValidator.cs b/src/lib/AutoBackend.Sdk/Services/PermissionValidator/IPermissionValidator.cs
new file mode 100644
index 0000000..fe4e086
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Services/PermissionValidator/IPermissionValidator.cs
@@ -0,0 +1,8 @@
+using AutoBackend.Sdk.Enums;
+
+namespace AutoBackend.Sdk.Services.PermissionValidator;
+
+internal interface IPermissionValidator
+{
+ void Validate(PermissionType type, HashSet? affectedProperties = null) where TEntity : class, new();
+}
\ No newline at end of file
diff --git a/src/lib/AutoBackend.Sdk/Services/PermissionValidator/PermissionValidator.cs b/src/lib/AutoBackend.Sdk/Services/PermissionValidator/PermissionValidator.cs
new file mode 100644
index 0000000..b1a537e
--- /dev/null
+++ b/src/lib/AutoBackend.Sdk/Services/PermissionValidator/PermissionValidator.cs
@@ -0,0 +1,106 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Cryptography;
+using AutoBackend.Sdk.Configuration;
+using AutoBackend.Sdk.Enums;
+using AutoBackend.Sdk.Exceptions.Data;
+using AutoBackend.Sdk.Services.EntityMetadataProvider;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+
+namespace AutoBackend.Sdk.Services.PermissionValidator;
+
+internal sealed class PermissionValidator(
+ IEntityMetadataProvider entityMetadataProvider,
+ IHttpContextAccessor httpContextAccessor,
+ IOptions options): IPermissionValidator
+{
+ public void Validate(PermissionType type, HashSet? affectedProperties) where TEntity : class, new()
+ {
+ var requiredPermissions = GetRequiredPermissions(type, affectedProperties ?? []);
+ if (requiredPermissions.Length == 0) return;
+ var givenPermissions = GetValidGivenPermissions();
+ CheckPermissions(givenPermissions, requiredPermissions);
+ }
+
+ private string[] GetRequiredPermissions(
+ PermissionType permissionType,
+ HashSet affectedProperties) where TEntity : class, new()
+ {
+ var permissions = new List();
+ var metadata = entityMetadataProvider.GetMetadata();
+ if (metadata.Permissions.HasFlag(permissionType))
+ permissions.Add(
+ string.Format(
+ Constants.GenericEntityPermissionName,
+ metadata.Name,
+ Enum.GetName(permissionType) ?? string.Empty));
+ permissions.AddRange(
+ metadata.Properties
+ .Where(property => affectedProperties.Contains(property.Name))
+ .Where(property => property.Permissions.HasFlag(permissionType))
+ .Select(property => string.Format(
+ Constants.GenericPropertyPermissionName,
+ metadata.Name,
+ property.Name,
+ Enum.GetName(permissionType) ?? string.Empty)));
+ return permissions.ToArray();
+ }
+
+ private string[] GetValidGivenPermissions()
+ {
+ var authorization = httpContextAccessor.HttpContext?.Request.Headers[Constants.AuthorizationHeaderName].FirstOrDefault();
+ if (authorization is not null &&
+ authorization.StartsWith(Constants.AuthorizationBearerPrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ var token = authorization[Constants.AuthorizationBearerPrefix.Length..].Trim();
+ if (!string.IsNullOrWhiteSpace(token))
+ {
+ var rsa = RSA.Create();
+ rsa.ImportFromPem(options.Value.PublicKey);
+ var parameters = new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new RsaSecurityKey(rsa)
+ };
+ if (!string.IsNullOrWhiteSpace(options.Value.ValidIssuer))
+ {
+ parameters.ValidateIssuer = true;
+ parameters.ValidIssuer = options.Value.ValidIssuer;
+ }
+ if (!string.IsNullOrWhiteSpace(options.Value.ValidAudience))
+ {
+ parameters.ValidateAudience = true;
+ parameters.ValidAudience = options.Value.ValidAudience;
+ }
+
+ var handler = new JwtSecurityTokenHandler();
+ try
+ {
+ var claimsPrincipal = handler.ValidateToken(token, parameters, out _);
+ return claimsPrincipal.Claims
+ .Where(claim => claim.Type == Constants.GenericPermissionsClaimType)
+ .Select(claim => claim.Value)
+ .ToArray();
+ }
+ catch (Exception exception)
+ {
+ throw new UnauthorizedAccessDataException(
+ Constants.Unauthorized,
+ exception);
+ }
+ }
+ }
+ return [];
+ }
+
+ private static void CheckPermissions(string[] givenPermissions, string[] requiredPermissions)
+ {
+ var missingPermissions = requiredPermissions.Except(givenPermissions).ToArray();
+ if (missingPermissions.Length > 0)
+ throw new UnauthorizedAccessDataException(
+ string.Format(
+ Constants.UnauthorizedMissingPermissions,
+ string.Join(", ", missingPermissions)));
+ }
+}
\ No newline at end of file
diff --git a/src/samples/Sample/Data/Budget.cs b/src/samples/Sample/Data/Budget.cs
index 5b06a8d..ada8d2d 100644
--- a/src/samples/Sample/Data/Budget.cs
+++ b/src/samples/Sample/Data/Budget.cs
@@ -25,6 +25,10 @@ namespace Sample.Data;
nameof(Name),
nameof(OwnerId)
)]
+[GenericCreatePermission]
+[GenericReadPermission]
+[GenericUpdatePermission]
+[GenericDeletePermission]
public class Budget
{
[GenericFilter]
diff --git a/src/samples/Sample/Data/Participating.cs b/src/samples/Sample/Data/Participating.cs
index 5ccba26..54af282 100644
--- a/src/samples/Sample/Data/Participating.cs
+++ b/src/samples/Sample/Data/Participating.cs
@@ -19,6 +19,10 @@ namespace Sample.Data;
nameof(UserId),
nameof(BudgetId)
)]
+[GenericCreatePermission]
+[GenericReadPermission]
+[GenericUpdatePermission]
+[GenericDeletePermission]
public class Participating
{
[GenericFilter]
diff --git a/src/samples/Sample/Data/Transaction.cs b/src/samples/Sample/Data/Transaction.cs
index 3ec6b07..e418714 100644
--- a/src/samples/Sample/Data/Transaction.cs
+++ b/src/samples/Sample/Data/Transaction.cs
@@ -26,8 +26,13 @@ namespace Sample.Data;
nameof(BudgetId),
nameof(Amount),
nameof(DateTimeUtc),
- nameof(Comment)
+ nameof(Comment),
+ nameof(SecretKey)
)]
+[GenericCreatePermission]
+[GenericReadPermission]
+[GenericUpdatePermission]
+[GenericDeletePermission]
public class Transaction
{
[GenericFilter]
@@ -51,6 +56,7 @@ public class Transaction
public Budget Budget { get; set; } = null!;
[GenericFilter]
+ [GenericUpdatePermission]
[Precision(20, 4)]
public decimal Amount { get; set; }
diff --git a/src/samples/Sample/Data/TransactionVersion.cs b/src/samples/Sample/Data/TransactionVersion.cs
index 5f97fcf..48e3899 100644
--- a/src/samples/Sample/Data/TransactionVersion.cs
+++ b/src/samples/Sample/Data/TransactionVersion.cs
@@ -29,6 +29,10 @@ namespace Sample.Data;
nameof(OriginalTransactionId),
nameof(VersionDateTimeUtc)
)]
+[GenericCreatePermission]
+[GenericReadPermission]
+[GenericUpdatePermission]
+[GenericDeletePermission]
public class TransactionVersion
{
[GenericFilter]
diff --git a/src/samples/Sample/Data/User.cs b/src/samples/Sample/Data/User.cs
index 54f493d..66086f8 100644
--- a/src/samples/Sample/Data/User.cs
+++ b/src/samples/Sample/Data/User.cs
@@ -24,6 +24,10 @@ namespace Sample.Data;
nameof(TimeZone),
nameof(ActiveBudgetId)
)]
+[GenericCreatePermission]
+[GenericReadPermission]
+[GenericUpdatePermission]
+[GenericDeletePermission]
public class User
{
[GenericFilter]
diff --git a/src/samples/Sample/appsettings.json b/src/samples/Sample/appsettings.json
index 4a62c8a..59a3675 100644
--- a/src/samples/Sample/appsettings.json
+++ b/src/samples/Sample/appsettings.json
@@ -7,11 +7,16 @@
},
"AllowedHosts": "*",
"Database": {
- "PrimaryProvider": "Postgres",
+ "PrimaryProvider": "InMemory",
"Providers": {
"InMemory": "InMemoryDatabase",
"SqlServer": "Server=localhost;Database=autobackend;User Id=sa;Password=sqlserver;TrustServerCertificate=True;",
- "Postgres": "Server=localhost;Port=5432;Database=autobackend;User Id=postgres;Password=postgres;"
+ "Postgres": "Server=localhost;Port=5432;Database=autobackend;User Id=postgres;Password=postgres;Include Error Detail=true;"
}
+ },
+ "Jwt": {
+ "PublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArxu0Xdvb2EBWW2qm9RVn\nt2x/t0LkoqKOYKfCdHhEfEvqZ4mszyfLRYWFcjju7k3ku4B/xU9lz7t5ZDIkiBiS\nbfav+iuh90lYKLvYW0R9sk5eCllEyJfz1jPiSptgmUzqdnaiUQrY2nQiEIBhR5JT\nfBYu1b7Bj518Mc46t7RWAK7MEOEVw8tET8qCauqEj6HYKub1F+5KUgQL68pIiFYJ\ndQtt1FWyq/WYXXvKHcr4r/tGSV7oNqYyeGT6Aps5IYSPqcW3SnqRMcnd6mOFtyx5\n0Tn/w3rUB/Ie/5N4L/rkl6JY9v7ly2Ygc3tQb69DpoKQRKTwKGBqA+cwME5d0j53\nGQIDAQAB\n-----END PUBLIC KEY-----",
+ "ValidIssuer": "AutoBackendServer",
+ "ValidAudience": "AutoBackendUser"
}
}