entfga

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 14, 2024 License: Apache-2.0 Imports: 17 Imported by: 1

README

entfga

entfga is an ent extension to create relationship tuples using ent Hooks

install

You can install entfga by running the following command:

go get github.com/theopenlane/iam/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/theopenlane/iam/entfga"
	"entgo.io/ent/entc"
)

func main() {
	// initialize the entfga extension
	entfgaExt := entfga.New(
		entfga.WithSoftDeletes(),
		entfga.WithSchemaPath(schemaPath),
	)

	// generate authz checks if you are using ent policies
	if err := entfgaExt.Config.GenerateAuthzChecks(); err != nil {
		log.Fatalf("generating authz checks: %v", err)
	}

	if err := entc.Generate("./schema",
		&gen.Config{},
		entc.Extensions(
            entfgaExt,
		),
	); 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
Usage

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},

Generate Hooks and Policies

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.

Generate Policies Only

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,
		},
	}
}

Using Policies

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(),
		},
	}
}

NOTE: These policies can only be added after an initial run of entc with the Annotations. This is what creates the CheckAccess, etc functions that are referenced in the policy above.

Contributing

Please read the contributing guide as well as the Developer Certificate of Origin. You will be required to sign all commits to the OpenLane project, so if you're unfamiliar with how to set that up, see github's documentation.

Security

We take the security of our software products and services seriously, including all of the open source code repositories managed through our Github Organizations, such as theopenlane. If you believe you have found a security vulnerability in any of our repositories, please report it to us through coordinated disclosure.

Please do NOT report security vulnerabilities through public github issues, discussions, or pull requests!

Instead, please send an email to security@theopenlane.io with as much information as possible to best help us understand and resolve the issues. See the security policy attached to this repository for more details.

Questions?

Open a github issue on this repository and we'll respond as soon as we're able!

Documentation

Overview

Package entfga is an ent extension that creates hooks for OpenFGA relationships

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnsupportedType is returned when the object type is not supported
	ErrUnsupportedType = errors.New("unsupported type")

	// ErrMissingRole is returned when an update request is made that contains no role
	ErrMissingRole = errors.New("missing role in update")

	// ErrFailedToGenerateTemplate is returned when the template cannot be generated
	ErrFailedToGenerateTemplate = errors.New("failed to generate template")

	// ErrFailedToWriteTemplate is returned when the template cannot be written
	ErrFailedToWriteTemplate = errors.New("failed to write template")
)

Functions

func AuthzHooks

func AuthzHooks[T Mutation]() []ent.Hook

AuthzHooks returns a list of authorization hooks for create, update, and delete operations on a specific type of mutation.

func On

func On(hk ent.Hook, op ent.Op) ent.Hook

On will execute the appropriate hook based on the ent operation

Types

type Annotations

type Annotations struct {
	ObjectType      string `yaml:"ObjectType,omitempty"`      // Object type for the fga relationship
	IncludeHooks    bool   `yaml:"includeHooks,omitempty"`    // Include hooks for the fga extension to add tuples to FGA
	IDField         string `yaml:"idField,omitempty"`         // ID field for the object type
	NillableIDField bool   `yaml:"nillableIDField,omitempty"` // NillableIDField set to true if the id is optional field in the ent schema
	OrgOwnedField   bool   `yaml:"orgOwnedField,omitempty"`   // OrgOwnedField set to true if the field is an org owned field and org automatically set by the system
}

Annotations of the fga extension

func (Annotations) Name

func (Annotations) Name() string

Name of the annotation

type AuthzExtension

type AuthzExtension struct {
	entc.DefaultExtension
	// contains filtered or unexported fields
}

AuthzExtension implements entc.Extension.

func New

func New(opts ...ConfigOption) *AuthzExtension

New creates a new fga extension with the provided config options

func (*AuthzExtension) Annotations

func (e *AuthzExtension) Annotations() []entc.Annotation

Annotations of the AuthzExtension

func (*AuthzExtension) GenerateAuthzChecks

func (e *AuthzExtension) GenerateAuthzChecks() error

GenerateAuthzChecks generates the authz checks for the ent schema this is separate to allow the function to be called outside the entc generation due to dependencies between the ent policies and the authz checks

func (*AuthzExtension) Templates

func (e *AuthzExtension) Templates() []*gen.Template

Templates returns the generated templates which include the client and authz from mutation

type Config

type Config struct {
	// SoftDeletes is used to determine if the schema uses soft deletes
	SoftDeletes bool
	// SchemaPath is the path to the schema directory
	SchemaPath string
	// GeneratedPath is the path to the generated directory
	GeneratedPath string
	// GeneratedPkg is the package that the generated code will be placed in
	GeneratedPkg string
}

func (Config) Name

func (c Config) Name() string

type ConfigOption

type ConfigOption = func(*Config)

func WithGeneratedPath

func WithGeneratedPath(generatedPath string) ConfigOption

WithGeneratedPath allows you to set an alternative ent generated path Defaults to "internal/ent/generated"

func WithGeneratedPkg

func WithGeneratedPkg(generatedPkg string) ConfigOption

WithGeneratedPkg allows you to set an alternative generated package Defaults to "generated"

func WithSchemaPath

func WithSchemaPath(schemaPath string) ConfigOption

WithSchemaPath allows you to set an alternative schemaPath Defaults to "./schema"

func WithSoftDeletes

func WithSoftDeletes() ConfigOption

WithSoftDeletes ensure the delete hook is still used even when soft deletes change the Op to Update

type Mutation

type Mutation interface {
	// Op is the ent operation being taken on the Mutation (Create, Update, UpdateOne, Delete, DeleteOne)
	Op() ent.Op
	// CreateTuplesFromCreate creates tuple relationships for the user/object type on Create Mutations
	CreateTuplesFromCreate(ctx context.Context) error
	// CreateTuplesFromUpdate creates new and deletes old tuple relationships for the user/object type on Update Mutations
	CreateTuplesFromUpdate(ctx context.Context) error
	// CreateTuplesFromDelete deletes tuple relationships for the user/object type on Delete Mutations
	CreateTuplesFromDelete(ctx context.Context) error
	// CheckAccessForEdit checks if the user has access to edit the object type
	CheckAccessForEdit(ctx context.Context) error
	// CheckAccessForDelete checks if the user has access to delete the object type
	CheckAccessForDelete(ctx context.Context) error
}

Mutation interface that all generated Mutation types must implement These functions (with the exception of Op() which is already created) are generated by the ent extension for every schema that includes the `entfga.NewFGAExtension“ extension to satisfy the interface If hooks are skipped by the mutation, the functions are created to satisfy the interface but always return nil and are not added to the client

type Mutator

type Mutator interface {
	Mutate(context.Context, Mutation) (ent.Value, error)
}

Mutator is an interface thats defines a method for mutating a generic ent value based on a given mutation. This is used as a generic interface that ent generated Mutations will implement

type OpType

type OpType string

OpType is the ent operation type in string form

const (
	// OpTypeInsert is the insert (create) operation
	OpTypeInsert OpType = "INSERT"
	// OpTypeUpdate is the update operation
	OpTypeUpdate OpType = "UPDATE"
	// OpTypeDelete is the delete operation
	OpTypeDelete OpType = "DELETE"
)

func (OpType) MarshalGQL

func (op OpType) MarshalGQL(w io.Writer)

MarshalGQL implement the Marshaler interface for gqlgen

func (*OpType) Scan

func (op *OpType) Scan(v any) error

Scan implements the `database/sql.Scanner` interface for the `OpType` type and is used to convert a value from the database into an `OpType` value.

func (OpType) String

func (op OpType) String() string

String value of the operation

func (*OpType) UnmarshalGQL

func (op *OpType) UnmarshalGQL(v interface{}) error

UnmarshalGQL implement the Unmarshaler interface for gqlgen

func (OpType) Value

func (op OpType) Value() (driver.Value, error)

Value of the operation type

func (OpType) Values

func (OpType) Values() (kinds []string)

Values provides list valid values for Enum.

type Querier

type Querier interface {
	Query(context.Context, Query) (ent.Value, error)
}

Querier is an interface thats defines a method for querying a generic ent value based on a given query. This is used as a generic interface that ent generated Query will implement

type Query

type Query interface {
	// Op is the ent operation being taken on the Mutation (Create, Update, UpdateOne, Delete, DeleteOne)
	Op() ent.Op

	// CheckAccess checks if the user has read access to the object type
	CheckAccess(ctx context.Context) error
}

Query interface that all generated Query types must implement

type Role

type Role string
var (
	RoleOwner  Role = "OWNER"
	RoleAdmin  Role = "ADMIN"
	RoleMember Role = "MEMBER"
	Invalid    Role = "INVALID"
)

func Enum

func Enum(r string) Role

Enum returns the Role based on string input

func (Role) MarshalGQL

func (r Role) MarshalGQL(w io.Writer)

MarshalGQL implement the Marshaler interface for gqlgen

func (Role) String

func (r Role) String() string

String returns the role as a string

func (*Role) UnmarshalGQL

func (r *Role) UnmarshalGQL(v interface{}) error

UnmarshalGQL implement the Unmarshaler interface for gqlgen

func (Role) Values

func (Role) Values() (kinds []string)

Values returns a slice of strings that represents all the possible values of the Role enum. Possible default values are "ADMIN", and "MEMBER".

Directories

Path Synopsis
_examples
basic/ent
Code generated by entfga, DO NOT EDIT.
Code generated by entfga, DO NOT EDIT.
basic/ent/enums
Package enums has enums
Package enums has enums

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL