Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support OpenFGA 0.3.0 #14

Merged
merged 2 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading