Go libraries to interact with OpenFGA
Wrappers to interact with the OpenFGA go-sdk and client libraries
You can install fgax
by running the following command:
go get github.com/datumforge/fgax@latest
Ent extension to create relationship tuples using Ent Hooks
You can install entfga
by running the following command:
go get github.com/datumforge/fgax/entfga@latest
In addition to installing entfga
, you need to create two files in your ent
directory: entc.go
and generate.go
.
The entc.go
file should contain the following code:
//go:build ignore
package main
import (
"log"
"github.com/datumforge/fgax/entfga"
"entgo.io/ent/entc"
)
func main() {
if err := entc.Generate("./schema",
&gen.Config{},
entc.Extensions(
entfga.NewFGAExtension(
entfga.WithSoftDeletes(),
),
),
); err != nil {
log.Fatal("running ent codegen:", err)
}
}
The generate.go
file should contain the following code:
package ent
//go:generate go run -mod=mod entc.go
When creating the *ent.Client
add the following to enable the authz hooks and policies:
client.WithAuthz()
The privacy
feature must be turned on:
Features: []gen.Feature{gen.FeaturePrivacy},
In the ent
schema, provide the following annotation:
// Annotations of the OrgMembership
func (OrgMembership) Annotations() []schema.Annotation {
return []schema.Annotation{
entfga.Annotations{
ObjectType: "organization",
IncludeHooks: true,
IDField: "OrganizationID", // Defaults to ID, override to object ID field
},
}
}
The ObjectType
must be the same between the ID field name in the schema and the object type in the FGA relationship. In the example above
the field in the schema is OrganizationID
and the object in FGA is organization
.
If the ID
field is Optional()
, you'll need to set NillableIDField: true,
on the annotation to ensure the string
value is used instead of the pointer
on the CreateInput
.
In the ent
schema, provide the following annotation:
// Annotations of the Organization
func (Organization) Annotations() []schema.Annotation {
return []schema.Annotation{
entfga.Annotations{
ObjectType: "organization",
IncludeHooks: false,
},
}
}
A policy check function will be created per mutation and query type when the annotation is used, these can be set on the policy of the schema.
They must be wrapped in the privacy
MutationRuleFunc
, as seen the example below:
// Policy of the Organization
func (Organization) Policy() ent.Policy {
return privacy.Policy{
Mutation: privacy.MutationPolicy{
rule.DenyIfNoSubject(),
privacy.OrganizationMutationRuleFunc(func(ctx context.Context, m *generated.OrganizationMutation) error {
return m.CheckAccessForEdit(ctx)
}),
// Add a separate delete policy if permissions for delete of the object differ from normal edit permissions
privacy.OrganizationMutationRuleFunc(func(ctx context.Context, m *generated.OrganizationMutation) error {
return m.CheckAccessForDelete(ctx)
}),
privacy.AlwaysDenyRule(),
},
Query: privacy.QueryPolicy{
privacy.OrganizationQueryRuleFunc(func(ctx context.Context, q *generated.OrganizationQuery) error {
return q.CheckAccess(ctx)
}),
privacy.AlwaysDenyRule(),
},
}
}
If you are using the soft delete mixin provided by entx, add the following option to the extension:
entfga.WithSoftDeletes(),
This will allow the hooks to delete tuples correctly after the ent.Op
is updated to a UpdateOne
from a DeleteOne