fgax

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: May 8, 2024 License: Apache-2.0 Imports: 17 Imported by: 2

README

Build status

fgax

Go libraries to interact with OpenFGA

Packages

fgax

Wrappers to interact with the OpenFGA go-sdk and client libraries

Installation

You can install fgax by running the following command:

go get github.com/datumforge/fgax@latest
entfga

Ent extension to create relationship tuples using Ent Hooks

Installation

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

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

Documentation

Overview

Package fgax includes client libraries to interact with openfga authorization credit to https://github.com/canonical/ofga/blob/main/tuples.go

Index

Constants

View Source
const (
	// SystemAdminRole is the role for system admins that have the highest level of access
	SystemAdminRole = "system_admin"
	// MemberRelation is the relation for members of an entity
	MemberRelation = "member"
	// AdminRelation is the relation for admins of an entity
	AdminRelation = "admin"
	// OwnerRelation is the relation for owners of an entity
	OwnerRelation = "owner"
	// ParentRelation is the relation for parents of an entity
	ParentRelation = "parent"
	// AssigneeRoleRelation is the relation for assignees of an entity
	RoleRelation = "assignee"
	// CanView is the relation for viewing an entity
	CanView = "can_view"
	// CanEdit is the relation for editing an entity
	CanEdit = "can_edit"
	// CanDelete is the relation for deleting an entity
	CanDelete = "can_delete"
)

setup relations for use in creating tuples

Variables

View Source
var (
	// ErrFGAMissingHost is returned when a host is not provided
	ErrFGAMissingHost = errors.New("invalid OpenFGA config: missing host")

	// ErrMissingRelation is returned when a relation is empty in a tuple creation
	ErrMissingRelation = errors.New("unable to create tuple, missing relation")

	// ErrInvalidAccessCheck is returned when a field required to check a tuple is empty
	ErrInvalidAccessCheck = errors.New("unable to check tuple, missing required field")

	// ErrMissingObject is returned when a object is empty in a tuple creation
	ErrMissingObject = errors.New("unable to create tuple, missing object")

	// ErrMissingObjectOnDeletion is returned when a object is empty in a tuple deletion
	ErrMissingObjectOnDeletion = errors.New("unable to delete tuple, missing object")

	// ErrFailedToTransformModel is returned when the FGA model cannot be transformed to JSON
	ErrFailedToTransformModel = errors.New("failed to transform fga model")
)

Functions

func Healthcheck

func Healthcheck(client Client) func(ctx context.Context) error

Healthcheck reads the model to check if the connection is working

func ListContains

func ListContains(entityType string, l []string, i string) bool

ListContains checks the results of an fga ListObjects and parses the entities to get the identifier to compare to another identifier based on entity type

Types

type AccessCheck added in v0.0.4

type AccessCheck struct {
	// ObjectType is the type of object being checked
	ObjectType Kind
	// ObjectID is the ID of the object being checked
	ObjectID string
	// Relation is the relationship being checked (e.g. "view", "edit", "delete")
	Relation string
	// UserID is the ID of the user making the request
	UserID string
	// SubjectType is the type of subject being checked
	SubjectType string
}

AccessCheck is a struct to hold the information needed to check access

type Client

type Client struct {
	// Ofga is the openFGA client
	Ofga ofgaclient.SdkClient
	// Config is the client configuration
	Config ofgaclient.ClientConfiguration
	// Logger is the provided Logger
	Logger *zap.SugaredLogger
}

Client is an ofga client with some configuration

func CreateFGAClientWithStore

func CreateFGAClientWithStore(ctx context.Context, c Config, l *zap.SugaredLogger) (*Client, error)

CreateFGAClientWithStore returns a Client with a store and model configured

func NewClient

func NewClient(host string, opts ...Option) (*Client, error)

NewClient returns a wrapped OpenFGA API client ensuring all calls are made to the provided authorization model (id) and returns what is necessary.

func NewMockFGAClient

func NewMockFGAClient(t *testing.T, c *mock_fga.MockSdkClient) *Client

NewMockFGAClient is a mock client based on the mockery testing framework

func (*Client) AddOrReplaceRole added in v0.1.7

func (c *Client) AddOrReplaceRole(ctx context.Context, r RoleRequest) error

AddOrReplaceRole adds (or replaces the existing) the role to the model and updates the config with the new model id

func (*Client) CheckAccess added in v0.0.4

func (c *Client) CheckAccess(ctx context.Context, ac AccessCheck) (bool, error)

CheckAccess checks if the user has access to the object type with the given relation

func (*Client) CheckGroupAccess

func (c *Client) CheckGroupAccess(ctx context.Context, userID, subjectType, groupID, relation string) (bool, error)

CheckGroupAccess checks if the user has access to the group with the given relation

func (*Client) CheckOrgAccess

func (c *Client) CheckOrgAccess(ctx context.Context, userID, subjectType, orgID, relation string) (bool, error)

CheckOrgAccess checks if the user has access to the organization with the given relation

func (*Client) CheckSystemAdminRole added in v0.1.4

func (c *Client) CheckSystemAdminRole(ctx context.Context, userID string) (bool, error)

CheckSystemAdminRole checks if the user has system admin access

func (*Client) CheckTuple

func (c *Client) CheckTuple(ctx context.Context, check ofgaclient.ClientCheckRequest) (bool, error)

CheckTuple checks the openFGA store for provided relationship tuple

func (*Client) CreateModel

CreateModel creates a new authorization model and returns the new model ID

func (*Client) CreateModelFromDSL added in v0.1.7

func (c *Client) CreateModelFromDSL(ctx context.Context, dsl []byte) (string, error)

CreateModelFromDSL creates a new fine grained authorization model from the DSL and returns the model ID

func (*Client) CreateModelFromFile added in v0.1.7

func (c *Client) CreateModelFromFile(ctx context.Context, fn string, forceCreate bool) (string, error)

CreateModelFromFile creates a new fine grained authorization model and returns the model ID

func (*Client) CreateStore

func (c *Client) CreateStore(ctx context.Context, storeName string) (string, error)

CreateStore creates a new fine grained authorization store and returns the store ID

func (*Client) DeleteAllObjectRelations

func (c *Client) DeleteAllObjectRelations(ctx context.Context, object string) error

func (*Client) DeleteRelationshipTuple

func (c *Client) DeleteRelationshipTuple(ctx context.Context, tuples []openfga.TupleKeyWithoutCondition) (*ofgaclient.ClientWriteResponse, error)

DeleteRelationshipTuple deletes a relationship tuple in the openFGA store

func (*Client) GetModelID

func (c *Client) GetModelID() string

func (*Client) ListObjectsRequest

func (c *Client) ListObjectsRequest(ctx context.Context, subjectID, subjectType, objectType, relation string) (*ofgaclient.ClientListObjectsResponse, error)

ListObjectsRequest creates the ClientListObjectsRequest and queries the FGA store for all objects with the user+relation

func (*Client) WriteTupleKeys

func (c *Client) WriteTupleKeys(ctx context.Context, writes []TupleKey, deletes []TupleKey) (*ofgaclient.ClientWriteResponse, error)

WriteTupleKeys takes a tuples keys, converts them to a client write request, which can contain up to 10 writes and deletes, and executes in a single transaction

type Config

type Config struct {
	// Enabled - checks this first before reading the config
	Enabled bool `json:"enabled" koanf:"enabled" jsonschema:"description=enables authorization checks with openFGA" default:"true"`
	// StoreName of the FGA Store
	StoreName string `json:"storeName" koanf:"storeName" jsonschema:"description=name of openFGA store" default:"datum"`
	// HostURL of the fga API, replaces Host and Scheme settings
	HostURL string `` /* 138-byte string literal not displayed */
	// StoreID of the authorization store in FGA
	StoreID string `json:"storeId" koanf:"storeId" jsonschema:"description=id of openFGA store"`
	// ModelID that already exists in authorization store to be used
	ModelID string `json:"modelId" koanf:"modelId" jsonschema:"description=id of openFGA model"`
	// CreateNewModel force creates a new model, even if one already exists
	CreateNewModel bool `` /* 138-byte string literal not displayed */
	// ModelFile is the path to the model file
	ModelFile string `json:"modelFile" koanf:"modelFile" jsonschema:"description=path to the fga model file" default:"fga/model/datum.fga"`
}

Config configures the openFGA setup

type Entity

type Entity struct {
	Kind       Kind
	Identifier string
	Relation   Relation
}

Entity represents an entity/entity-set in OpenFGA. Example: `user:<user-id>`, `org:<org-id>#member`

func ParseEntity

func ParseEntity(s string) (Entity, error)

ParseEntity will parse a string representation into an Entity. It expects to find entities of the form:

  • <entityType>:<Identifier> eg. organization:datum
  • <entityType>:<Identifier>#<relationship-set> eg. organization:datum#member

func (*Entity) String

func (e *Entity) String() string

String returns a string representation of the entity/entity-set.

type InvalidEntityError

type InvalidEntityError struct {
	EntityRepresentation string
}

InvalidEntityError is returned when an invalid openFGA entity is configured

func (*InvalidEntityError) Error

func (e *InvalidEntityError) Error() string

Error returns the InvalidEntityError in string format

type Kind

type Kind string

Kind represents the type of the entity in OpenFGA.

func (Kind) String

func (k Kind) String() string

String implements the Stringer interface.

type Option

type Option func(c *Client)

Option is a functional configuration option for openFGA client

func WithAuthorizationModelID

func WithAuthorizationModelID(authModelID string) Option

WithAuthorizationModelID sets the authorization model ID

func WithLogger

func WithLogger(l *zap.SugaredLogger) Option

WithLogger sets logger

func WithStoreID

func WithStoreID(storeID string) Option

WithStoreID sets the store IDs, not needed when calling `CreateStore` or `ListStores`

func WithToken

func WithToken(token string) Option

WithToken sets the client credentials

type Relation

type Relation string

Relation represents the type of relation between entities in OpenFGA.

func (Relation) String

func (r Relation) String() string

String implements the Stringer interface.

type RelationCombination added in v0.1.7

type RelationCombination string

RelationCombination is the combination of the relation as an `and`, `or`, or `not`

const (
	// Union is an `or` relation
	Union RelationCombination = "union"
	// Intersection is an `and` relation
	Intersection RelationCombination = "intersection"
)

type RelationSetting added in v0.1.7

type RelationSetting struct {
	// Relation is the relation to the object
	Relation string
	// IsDirect is the direct relation to another fga object type
	IsDirect bool
	// FromRelation is the relation from another relation, leave empty if not a from relation
	FromRelation string
}

RelationSetting includes the name of the relation as well as flags to determine the type of relation

type RoleRequest added in v0.1.7

type RoleRequest struct {
	// Role is the relation to add to the model
	Role string
	// Relation is the relation to the object
	Relations []RelationSetting
	// RelationCombination is the combination of the relation
	RelationCombination RelationCombination
	// ObjectType is the object type to add the role to
	ObjectType string
}

RoleRequest is the request to add a role to the model for an existing object

type TupleKey

type TupleKey struct {
	Subject  Entity
	Object   Entity
	Relation Relation `json:"relation"`
}

func GetTupleKey

func GetTupleKey(subjectID, subjectType, objectID, objectType, relation string) TupleKey

GetTupleKey creates a Tuple key with the provided subject, object, and role

func NewTupleKey

func NewTupleKey() TupleKey

type WritingTuplesError

type WritingTuplesError struct {
	User          string
	Relation      string
	Object        string
	Operation     string
	ErrorResponse error
}

WritingTuplesError is returned when an error is returned writing a relationship tuple

func (*WritingTuplesError) Error

func (e *WritingTuplesError) Error() string

Error returns the InvalidEntityError in string format

Directories

Path Synopsis
Package entfga is an ent extension that creates hooks for OpenFGA relationships
Package entfga is an ent extension that creates hooks for OpenFGA relationships
Package client includes the mock FGA client generated by testify mockery
Package client includes the mock FGA client generated by testify mockery

Jump to

Keyboard shortcuts

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