Skip to content

Commit

Permalink
Support OpenFGA 0.3.0 (#14)
Browse files Browse the repository at this point in the history
* Support OpenFGA 0.3.0

* Fix build
  • Loading branch information
Hawxy authored Jan 5, 2024
1 parent f2574f6 commit 41e0934
Show file tree
Hide file tree
Showing 14 changed files with 134 additions and 115 deletions.
2 changes: 1 addition & 1 deletion Package.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>1.0.0</Version>
<Version>1.1.0</Version>
<Authors>Hawxy</Authors>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ The `ConfigureAuth0Fga` extension will configure the client to work with the Aut

### OpenFGA

1. Add the FGA `ApiScheme`, `ApiHost` & `StoreId` to your application configuration.
1. Add the FGA `ApiUrl` & `StoreId` to your application configuration.
2. Add the following code to your ASP.NET Core configuration:
```cs
services.AddOpenFgaClient(config =>
{
config.ConfigureOpenFga(x =>
{
x.SetConnection(context.Configuration["Fga:ApiScheme"] context.Configuration["Fga:ApiHost"]);
x.SetConnection(context.Configuration["Fga:ApiUrl"]);
});
config.SetStoreId(context.Configuration["Fga:StoreId"]);
});
Expand All @@ -64,7 +64,7 @@ Authentication can be added to OpenFGA connections via the relevant extensions:
```csharp
config.ConfigureOpenFga(x =>
{
x.SetConnection(Uri.UriSchemeHttp, context.Configuration["Fga:ApiHost"]);
x.SetConnection(context.Configuration["Fga:ApiUrl"]);

// Add API key auth
x.WithApiKeyAuthentication(context.Configuration["Fga:ApiKey"]);
Expand Down Expand Up @@ -187,7 +187,7 @@ services.PostConfigureFgaClient(config =>
config.SetStoreId(storeId);
config.ConfigureOpenFga(x =>
{
x.SetConnection(Uri.UriSchemeHttp, openFgaUrl);
x.SetConnection(context.Configuration["Fga:ApiUrl"]);
});
});

Expand All @@ -200,7 +200,7 @@ services.PostConfigureFgaClient(config =>
To get started:

1. Install `Fga.Net.DependencyInjection`
2. Add your `StoreId`, `ClientId` and `ClientSecret` Auth0 FGA configuration **OR** `ApiScheme`, `ApiHost` & `StoreId` OpenFGA configuration to your application configuration, ideally via the [dotnet secrets manager](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows#enable-secret-storage).
2. Add your `StoreId`, `ClientId` and `ClientSecret` Auth0 FGA configuration **OR** `ApiUrl` & `StoreId` OpenFGA configuration to your application configuration, ideally via the [dotnet secrets manager](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows#enable-secret-storage).
3. Register the authorization client:

```cs
Expand All @@ -222,7 +222,7 @@ var host = Host.CreateDefaultBuilder(args)
{
config.ConfigureOpenFga(x =>
{
x.SetConnection(Uri.UriSchemeHttp, context.Configuration["Fga:ApiHost"]);
x.SetConnection(context.Configuration["Fga:ApiUrl"]);

// Optionally add authentication settings
x.WithApiKeyAuthentication(context.Configuration["Fga:ApiKey"]);
Expand Down
2 changes: 1 addition & 1 deletion samples/Fga.Example.AspNetCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
{
x.ConfigureOpenFga(x =>
{
x.SetConnection(builder.Configuration["Fga:ApiScheme"]!, builder.Configuration["Fga:ApiHost"]!);
x.SetConnection(context.Configuration["Fga:ApiUrl"]!);
});
x.SetStoreId(builder.Configuration["Fga:StoreId"]);
Expand Down
18 changes: 9 additions & 9 deletions samples/Fga.Example.GenericHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@
{
config.ConfigureAuth0Fga(x =>
{
x.WithAuthentication(context.Configuration["Auth0Fga:ClientId"], context.Configuration["Auth0Fga:ClientSecret"]);
x.WithAuthentication(context.Configuration["Auth0Fga:ClientId"]!, context.Configuration["Auth0Fga:ClientSecret"]!);
});
config.SetStoreId(context.Configuration["Auth0Fga:StoreId"]);
config.SetStoreId(context.Configuration["Auth0Fga:StoreId"]!);
});
// OpenFGA
services.AddOpenFgaClient(config =>
{
config.ConfigureOpenFga(x =>
{
x.SetConnection(Uri.UriSchemeHttp, context.Configuration["Fga:ApiHost"]);
x.SetConnection(context.Configuration["Fga:ApiUrl"]!);
// Optionally add authentication settings
x.WithApiKeyAuthentication(context.Configuration["Fga:ApiKey"]);
x.WithApiKeyAuthentication(context.Configuration["Fga:ApiKey"]!);
x.WithOidcAuthentication(
context.Configuration["Fga:ClientId"],
context.Configuration["Fga:ClientSecret"],
context.Configuration["Fga:Issuer"],
context.Configuration["Fga:Audience"]);
context.Configuration["Fga:ClientId"]!,
context.Configuration["Fga:ClientSecret"]!,
context.Configuration["Fga:Issuer"]!,
context.Configuration["Fga:Audience"]!);
});
config.SetStoreId(context.Configuration["Fga:StoreId"]);
config.SetStoreId(context.Configuration["Fga:StoreId"]!);
});
services.AddHostedService<MyBackgroundWorker>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,81 +37,80 @@ public FineGrainedAuthorizationHandler(IFgaCheckDecorator client, ILogger<FineGr
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, FineGrainedAuthorizationRequirement requirement)
{
if (context.Resource is HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
if (endpoint is null)
return;
var attributes = endpoint.Metadata.GetOrderedMetadata<FgaAttribute>();
// The user is enforcing the fga policy but there's no attributes here.
if (attributes.Count == 0)
return;
if (context.Resource is not HttpContext httpContext)
throw new InvalidOperationException($"{nameof(FineGrainedAuthorizationHandler)} called with invalid resource type. This handler is only compatible with endpoint routing.");

var endpoint = httpContext.GetEndpoint();
if (endpoint is null)
return;
var attributes = endpoint.Metadata.GetOrderedMetadata<FgaAttribute>();
// The user is enforcing the fga policy but there's no attributes here.
if (attributes.Count == 0)
return;

var checks = new List<ClientCheckRequest>();
var checks = new List<ClientCheckRequest>();

foreach (var attribute in attributes)
foreach (var attribute in attributes)
{
string? user;
string? relation;
string? @object;
try
{
string? user;
string? relation;
string? @object;
try
{
user = await attribute.GetUser(httpContext);
relation = await attribute.GetRelation(httpContext);
@object = await attribute.GetObject(httpContext);
}
catch (FgaMiddlewareException ex)
{
_logger.MiddlewareException(ex);
return;
}
user = await attribute.GetUser(httpContext);
relation = await attribute.GetRelation(httpContext);
@object = await attribute.GetObject(httpContext);
}
catch (FgaMiddlewareException ex)
{
_logger.MiddlewareException(ex);
return;
}

// If we get back nulls from anything we cannot perform a check.
if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(relation) || string.IsNullOrEmpty(@object))
{
_logger.NullValuesReturned(user, relation, @object);
return;
}
// If we get back nulls from anything we cannot perform a check.
if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(relation) || string.IsNullOrEmpty(@object))
{
_logger.NullValuesReturned(user, relation, @object);
return;
}

if (!Validation.IsValidUser(user))
{
_logger.InvalidUser(user);
return;
}

checks.Add(new ClientCheckRequest
{
User = user,
Relation = relation,
Object = @object
});
if (!Validation.IsValidUser(user))
{
_logger.InvalidUser(user);
return;
}

checks.Add(new ClientCheckRequest
{
User = user,
Relation = relation,
Object = @object
});
}

var results = await _client.BatchCheck(checks, httpContext.RequestAborted);
var results = await _client.BatchCheck(checks, httpContext.RequestAborted);

var failedChecks = results.Responses.Where(x=> x.Allowed is false).ToArray();
var failedChecks = results.Responses.Where(x=> x.Allowed is false).ToArray();

// log all of reasons for the failed checks
if (failedChecks.Length > 0)
// log all of reasons for the failed checks
if (failedChecks.Length > 0)
{
foreach (var response in failedChecks)
{
foreach (var response in failedChecks)
if (response.Error is not null)
{
if (response.Error is not null)
{
_logger.CheckException(response.Request.User, response.Request.Relation, response.Request.Object, response.Error);
}
else if (response.Allowed is false)
{
_logger.CheckFailure(response.Request.User, response.Request.Relation, response.Request.Object);
}
_logger.CheckException(response.Request.User, response.Request.Relation, response.Request.Object, response.Error);
}
else if (response.Allowed is false)
{
_logger.CheckFailure(response.Request.User, response.Request.Relation, response.Request.Object);
}
}
else
{
context.Succeed(requirement);
}
}
else
{
context.Succeed(requirement);
}

}


}
}
2 changes: 1 addition & 1 deletion src/Fga.Net.AspNetCore/Fga.Net.AspNetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
6 changes: 3 additions & 3 deletions src/Fga.Net/Configuration/Auth0FgaConnectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public enum Auth0Environment
Us
}

internal sealed record Auth0FgaEnvironment(string Scheme, string ApiHost, string ApiTokenIssuer, string ApiAudience);
internal sealed record Auth0FgaEnvironment(string ApiHost, string ApiTokenIssuer, string ApiAudience);


/// <summary>
Expand All @@ -45,7 +45,7 @@ public sealed class Auth0FgaConnectionBuilder
{
{
Auth0Environment.Us,
new Auth0FgaEnvironment(Uri.UriSchemeHttps, "api.us1.fga.dev", "fga.us.auth0.com", "https://api.us1.fga.dev/")
new Auth0FgaEnvironment("https://api.us1.fga.dev", "fga.us.auth0.com", "https://api.us1.fga.dev/")
}
};

Expand Down Expand Up @@ -88,6 +88,6 @@ internal FgaConnectionConfiguration Build()
}
};

return new FgaConnectionConfiguration(environment.Scheme, environment.ApiHost, credentials);
return new FgaConnectionConfiguration(environment.ApiHost, credentials);
}
}
24 changes: 17 additions & 7 deletions src/Fga.Net/Configuration/OpenFgaConnectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,29 @@ namespace Fga.Net.DependencyInjection.Configuration;
/// </summary>
public sealed class OpenFgaConnectionBuilder
{
private string _apiScheme = Uri.UriSchemeHttps;
private string? _apiHost;
private string? _apiUrl;

/// <summary>
/// Sets the connection configuration for the host.
/// </summary>
/// <param name="apiScheme">API scheme, either http or https.</param>
/// <param name="apiHost">API host, should be in be plain URI format</param>
/// <returns></returns>
[Obsolete("Passing in a split scheme & host is obsolete and will be removed in a future release. Use SetConnection(string apiUrl)")]
public OpenFgaConnectionBuilder SetConnection(string apiScheme, string apiHost)
{
_apiScheme = apiScheme;
_apiHost = apiHost;
_apiUrl = $"{apiScheme}://{apiHost}";
return this;
}

/// <summary>
/// Sets the connection configuration for the host.
/// </summary>
/// <param name="apiUrl">The URL for the API, should include scheme + domain. Can optionally include port.</param>
/// <returns></returns>
public OpenFgaConnectionBuilder SetConnection(string apiUrl)
{
_apiUrl = apiUrl;
return this;
}

Expand Down Expand Up @@ -85,9 +95,9 @@ public void WithOidcAuthentication(string clientId, string clientSecret, string

internal FgaConnectionConfiguration Build()
{
if (string.IsNullOrEmpty(_apiHost))
if (string.IsNullOrEmpty(_apiUrl))
throw new InvalidOperationException("API Host cannot be null or empty");
if (_apiScheme != Uri.UriSchemeHttps && _apiScheme != Uri.UriSchemeHttp)
if (!_apiUrl.Contains(Uri.UriSchemeHttps) && !_apiUrl.Contains(Uri.UriSchemeHttp))
throw new InvalidOperationException("API Scheme must be http or https");
if (_credentials?.Method == CredentialsMethod.ApiToken && string.IsNullOrEmpty(_credentials.Config?.ApiToken))
throw new InvalidOperationException("API Key cannot be empty");
Expand All @@ -98,6 +108,6 @@ internal FgaConnectionConfiguration Build()
|| string.IsNullOrEmpty(_credentials.Config?.ApiAudience)))
throw new InvalidOperationException("Clients credential configuration cannot be contain missing values.");

return new FgaConnectionConfiguration(_apiScheme, _apiHost, _credentials);
return new FgaConnectionConfiguration(_apiUrl, _credentials);
}
}
11 changes: 9 additions & 2 deletions src/Fga.Net/Fga.Net.DependencyInjection.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="OpenFga.Sdk" Version="0.2.5" />
<PackageReference Include="OpenFga.Sdk" Version="0.3.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
</ItemGroup>

<Import Project="../../Package.Build.props" />
Expand Down
2 changes: 1 addition & 1 deletion src/Fga.Net/FgaConnectionConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ internal sealed record FgaBuiltConfiguration(
int? MinWaitInMs,
FgaConnectionConfiguration Connection);

internal sealed record FgaConnectionConfiguration(string ApiScheme, string ApiHost, Credentials? Credentials);
internal sealed record FgaConnectionConfiguration(string ApiUrl, Credentials? Credentials);
3 changes: 1 addition & 2 deletions src/Fga.Net/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ public static void PostConfigureFgaClient(this IServiceCollection collection, Ac

private static void ConfigureFgaOptions(this FgaClientConfiguration x, FgaBuiltConfiguration config)
{
x.ApiScheme = config.Connection.ApiScheme;
x.ApiHost = config.Connection.ApiHost;
x.ApiUrl = config.Connection.ApiUrl;

x.StoreId = config.StoreId;
x.AuthorizationModelId = config.AuthorizationModelId;
Expand Down
Loading

0 comments on commit 41e0934

Please sign in to comment.