opadata

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Feb 27, 2024 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultQueryTemplate        = `allow_%s`
	DefaultPartialQueryTemplate = `filter_%s`
)
View Source
const (
	TagOPA              = `opa`
	TagDelimiter        = `,`
	TagAssignment       = `:`
	TagValueIgnore      = "-"
	TagKeyInputField    = `field`
	TagKeyInputFieldAlt = `input`
	TagKeyResourceType  = `type`
	TagKeyOPAPackage    = `package`
)

Variables

View Source
var (
	ErrQueryTranslation = opa.NewError(`generic query translation error`)
	ErrUnsupportedUsage = opa.NewError(`generic unsupported usage error`)
)

Functions

func FilterByPolicies

func FilterByPolicies(flags ...DBOperationFlag) func(*gorm.DB) *gorm.DB

FilterByPolicies is used as a scope for gorm.DB to override policy-based data filtering. The specified operations are enabled, and the rest are disabled e.g. db.WithContext(ctx).Scopes(FilterByPolicies(DBOperationFlagRead)).Find(...) Using this scope without context would panic

func FilterWithExtraData

func FilterWithExtraData(kvs ...string) func(*gorm.DB) *gorm.DB

FilterWithExtraData is used as a scope for gorm.DB to provide extra key-value pairs as input during policy-based data filtering. The extra KV pairs are added under `input.resource` e.g. db.WithContext(ctx).Scopes(FilterWithExtraData("exception", "ignore_tenancy")).Find(...)

func FilterWithQueries

func FilterWithQueries(op DBOperationFlag, query string, more ...interface{}) func(*gorm.DB) *gorm.DB

FilterWithQueries is used as a scope for gorm.DB to override policy-based data filtering. Used to customize queries of specified DB operation. Additional DBOperationFlag-string pairs can be provided. e.g. db.WithContext(ctx).Scopes(FilterWithQueries(DBOperationFlagRead, "resource.type.allow_read")).Find(...) Important: This scope accept FULL QUERY including policy package. Notes:

  • It's recommended to use dotted format without leading "data.". FilteredModel would adjust the format based on operation. e.g. "resource.type.allow_read"
  • This scope doesn't enable/disable data-filtering. It only overrides queries set in tag.
  • Using this scope without context would panic
  • Having incorrect parameters cause panic

func ResolveResource

func ResolveResource[ModelType any](model *ModelType) (resType string, resValues *opa.ResourceValues, err error)

ResolveResource parse given model and resolve resource type and resource values using "opa" tags Typically used together with opa.AllowResource as manual policy enforcement. ModelType should be model struct with FilteredModel and valid "opa" tags. Note: resValues could be nil if all OPA related values are zero

func SkipFiltering

func SkipFiltering() func(*gorm.DB) *gorm.DB

SkipFiltering is used as a scope for gorm.DB to skip policy-based data filtering e.g. db.WithContext(ctx).Scopes(SkipFiltering()).Find(...) Using this scope without context would panic

Types

type DBOperationFlag

type DBOperationFlag uint

DBOperationFlag bitwise Flag of tenancy flag mode

const (
	DBOperationFlagCreate DBOperationFlag = 1 << iota
	DBOperationFlagRead
	DBOperationFlagUpdate
	DBOperationFlagDelete
)

func (DBOperationFlag) MarshalText

func (f DBOperationFlag) MarshalText() ([]byte, error)

func (*DBOperationFlag) UnmarshalText

func (f *DBOperationFlag) UnmarshalText(data []byte) error

type Filter

type Filter struct {
	// contains filtered or unexported fields
}

Filter is a marker type that can be used in model struct as Struct Field. It's responsible for automatically applying OPA policy-based data filtering on model fields with "opa" tag.

Filter uses following GORM interfaces to modify PostgreSQL statements during select/update/delete, and apply value checks during create/update:

  • schema.QueryClausesInterface
  • schema.UpdateClausesInterface
  • schema.DeleteClausesInterface
  • schema.CreateClausesInterface

When Filter is present in data model, any model's fields tagged with "opa" will be used by OPA engine as following:

  • During "create", values are included with path "input.resource.<opa_field_name>"
  • During "update", values are included with path "input.resources.delta.<opa_field_name>"
  • During "select/update/delete", "input.resources.<opa_field_name>" is used as "unknowns" during OPA Partial Evaluation, and the result is translated to "WHERE" clause in PostgreSQL

Where "<opa_field_name>" is specified by "opa" tag as `opa:"field:<opa_field_name>"`

Usage:

Filter is used as type of Struct Field within model struct:

  • "opa" tag is required on the field with resource type defined: `opa:"type:<opa_res_type>"`
  • `gorm:"-"` is required
  • the field need to be exported

Examples:

type Model struct {
	ID        uuid.UUID `gorm:"primaryKey;type:uuid;default:gen_random_uuid();"`
	Value     string
	OwnerName string
	OwnerID   uuid.UUID            `gorm:"type:KeyID;not null" opa:"field:owner_id"`
	Sharing   constraints.Sharing  `opa:"field:sharing"`
	OPAFilter opadata.Filter `gorm:"-" opa:"type:my_resource"`
}

Note: OPA filtering on relationships are currently not supported

Supported Tags:

OPA tag should be in format of:

`opa:"<key>:<value,<key>:<value>,..."`

Invalid format or use of unsupported tag keys will result schema parsing error.

Supported tag keys are:

  • "field:<opa_input_field_name>": required on any data field in model, only applicable on data fields
  • "input:<opa_input_field_name>": "input" is an alias of "field", only applicable on data fields
  • "type:<opa_resource_type>": required on FilteredModel. Ignored on other fields. This value will be used as prefix/package of OPA policy: e.g. "<opa_resource_type>/<policy_name>"

Following keys can override CRUD policies and only applicable on FilteredModel:

  • "create:<policy_name>": optional, override policy used in OPA during create.
  • "read:<policy_name>": optional, override policy used in OPA during read.
  • "update:<policy_name>": optional, override policy used in OPA during update.
  • "delete:<policy_name>": optional, override policy used in OPA during delete.
  • "package:<policy_package>": optional, override policy's package. Default is "resource.<opa_resource_type>"

Note: When <policy_name> is "-", policy-based data filtering is disabled for that operation. The default values are "filter_<op>"

func (Filter) CreateClauses

func (pf Filter) CreateClauses(f *schema.Field) []clause.Interface

CreateClauses implements schema.CreateClausesInterface,

func (Filter) DeleteClauses

func (pf Filter) DeleteClauses(f *schema.Field) []clause.Interface

DeleteClauses implements schema.DeleteClausesInterface,

func (Filter) QueryClauses

func (pf Filter) QueryClauses(f *schema.Field) []clause.Interface

QueryClauses implements schema.QueryClausesInterface,

func (Filter) UpdateClauses

func (pf Filter) UpdateClauses(f *schema.Field) []clause.Interface

UpdateClauses implements schema.UpdateClausesInterface,

type FilteredModel

type FilteredModel struct {
	PolicyFilter policyFilter `gorm:"-"`
}

FilteredModel is a marker type that can be used in model struct as Embedded Struct. It's responsible for automatically applying OPA policy-based data filtering on model fields with "opa" tag.

FilteredModel uses following GORM interfaces to modify PostgreSQL statements during select/update/delete, and apply value checks during create/update:

  • schema.QueryClausesInterface
  • schema.UpdateClausesInterface
  • schema.DeleteClausesInterface
  • schema.CreateClausesInterface

When FilteredModel is present in data model, any model's fields tagged with "opa" will be used by OPA engine as following:

  • During "create", values are included with path "input.resource.<opa_field_name>"
  • During "update", values are included with path "input.resources.delta.<opa_field_name>"
  • During "select/update/delete", "input.resources.<opa_field_name>" is used as "unknowns" during OPA Partial Evaluation, and the result is translated to "WHERE" clause in PostgreSQL

Where "<opa_field_name>" is specified by "opa" tag as `opa:"field:<opa_field_name>"`

Usage:

FilteredModel is used as Embedded Struct in model struct,

  • "opa" tag is required with resource type defined: `opa:"type:<opa_res_type>"`
  • "gorm" tag should not be applied to the embedded struct

Examples:

type Model struct {
	ID              uuid.UUID `gorm:"primaryKey;type:uuid;default:gen_random_uuid();"`
	Value           string
	TenantID        uuid.UUID            `gorm:"type:KeyID;not null" opa:"field:tenant_id"`
	TenantPath      pqx.UUIDArray        `gorm:"type:uuid[];index:,type:gin;not null" opa:"field:tenant_path"`
	OwnerID         uuid.UUID            `gorm:"type:KeyID;not null" opa:"field:owner_id"`
	opadata.FilteredModel `opa:"type:my_resource"`
}

Note: OPA filtering on relationships are currently not supported

Supported Tags:

OPA tag should be in format of:

`opa:"<key>:<value,<key>:<value>,..."`

Invalid format or use of unsupported tag keys will result schema parsing error.

Supported tag keys are:

  • "field:<opa_input_field_name>": required on any data field in model, only applicable on data fields
  • "input:<opa_input_field_name>": "input" is an alias of "field", only applicable on data fields
  • "type:<opa_resource_type>": required on FilteredModel. Ignored on other fields. This value will be used as prefix/package of OPA policy: e.g. "<opa_resource_type>/<policy_name>"

Following keys can override CRUD policies and only applicable on FilteredModel:

  • "create:<policy_name>": optional, override policy used in OPA during create.
  • "read:<policy_name>": optional, override policy used in OPA during read.
  • "update:<policy_name>": optional, override policy used in OPA during update.
  • "delete:<policy_name>": optional, override policy used in OPA during delete.
  • "package:<policy_package>": optional, override policy's package. Default is "resource.<opa_resource_type>"

Note: When <policy_name> is "-", policy-based data filtering is disabled for that operation. The default values are "filter_<op>"

type GormMapperConfig

type GormMapperConfig struct {
	Metadata  *Metadata
	Fields    map[string]*TaggedField
	Statement *gorm.Statement
}

type GormPartialQueryMapper

type GormPartialQueryMapper struct {
	// contains filtered or unexported fields
}

func NewGormPartialQueryMapper

func NewGormPartialQueryMapper(cfg *GormMapperConfig) *GormPartialQueryMapper

func (*GormPartialQueryMapper) And

func (*GormPartialQueryMapper) Comparison

func (m *GormPartialQueryMapper) Comparison(ctx context.Context, op ast.Ref, colRef ast.Ref, val interface{}) (ret clause.Expression, err error)

func (*GormPartialQueryMapper) Context

func (m *GormPartialQueryMapper) Context() context.Context

func (*GormPartialQueryMapper) MapResults

func (m *GormPartialQueryMapper) MapResults(pq *rego.PartialQueries) (interface{}, error)

func (*GormPartialQueryMapper) Negate

func (*GormPartialQueryMapper) Or

func (*GormPartialQueryMapper) Quote

func (m *GormPartialQueryMapper) Quote(field interface{}) string

func (*GormPartialQueryMapper) ResolveColumnExpr

func (m *GormPartialQueryMapper) ResolveColumnExpr(_ context.Context, field *TaggedField, paths ...string) string

ResolveColumnExpr resolve column clause with given field and optional JSONB path

func (*GormPartialQueryMapper) ResolveField

func (m *GormPartialQueryMapper) ResolveField(_ context.Context, colRef ast.Ref) (ret *TaggedField, jsonbPath []string, err error)

func (*GormPartialQueryMapper) ResolveValueExpr

func (m *GormPartialQueryMapper) ResolveValueExpr(_ context.Context, val interface{}, field *TaggedField) interface{}

func (*GormPartialQueryMapper) ResultToJSON

func (m *GormPartialQueryMapper) ResultToJSON(result interface{}) (interface{}, error)

func (*GormPartialQueryMapper) WithContext

type Metadata

type Metadata struct {
	OPATag
	Fields map[string]*TaggedField
	Schema *schema.Schema
}

Metadata contains all static/declarative information of a model struct.

type OPATag

type OPATag struct {
	// InputField Required on "to-be-filtered-by" model fields. Specify mappings between model field and OPA input fields.
	// e.g. `opa:"field:myProperty"` translate to `input.resource.myProperty` in OPA input
	InputField string

	// ResType Required on FilteredModel. This value contributes to both OPA query and OPA input:
	// - ResType is set to OPA input as `input.resource.type`
	// - Unless OPAPackage or Policies is specified, ResType is also part of OPA query:
	//   "data.resource.{{RestType}}.<filter|allow>_{{DBOperationFlag}}"
	ResType string

	// OPAPackage Optional on FilteredModel. Used to overwrite default OPA query.
	// Resulting query is "data.{{OPAPackage}}.<filter|allow>_{{DBOperationFlag}}"
	// e.g. `opa:"type:my_res, package:my.res" -> the OPA query is "data.my.res.filter_{{DBOperationFlag}}"
	OPAPackage string

	// Policies Optional on FilteredModel. Fine control of OPA queries for each type of DB operation.
	// - If set to "-", the corresponding DB operation is disabled for data-filtering.
	// 	 e.g. `opa:"type:my_res, read:-"` disables OPA data filtering for read operations (SELECT statements)
	// - If set to any other non-empty string, it's used to construct OPA query
	// 	 e.g. `opa:"type:my_res, read:my_custom_filter"` -> OPA query "data.resource.my_res.my_custom_filter" is used for read operations.
	Policies map[DBOperationFlag]string
	// contains filtered or unexported fields
}

OPATag supported key-value pairs in `opa` tag. `opa` tag is in format of `opa:"<key>:<value>, [<more_keys>:<more_values>, ...]". Unless specified, each key-value pair only takes effect on either "to-be-filtered-by" model fields (Model Fields) or FilteredModel (regardless if embedded or as a field), but not both.

func (*OPATag) Queries

func (t *OPATag) Queries() map[DBOperationFlag]string

Queries normalized queries for OPA. By default, queries are

func (*OPATag) UnmarshalText

func (t *OPATag) UnmarshalText(data []byte) error

type TaggedField

type TaggedField struct {
	schema.Field
	OPATag       OPATag
	RelationPath TaggedRelationPath
}

func (TaggedField) InputField

func (f TaggedField) InputField() string

type TaggedRelationPath

type TaggedRelationPath []*TaggedRelationship

func (TaggedRelationPath) InputField

func (path TaggedRelationPath) InputField() string

type TaggedRelationship

type TaggedRelationship struct {
	schema.Relationship
	OPATag OPATag
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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