Documentation ¶
Index ¶
- Constants
- Variables
- func FilterByPolicies(flags ...DBOperationFlag) func(*gorm.DB) *gorm.DB
- func FilterWithExtraData(kvs ...string) func(*gorm.DB) *gorm.DB
- func FilterWithQueries(op DBOperationFlag, query string, more ...interface{}) func(*gorm.DB) *gorm.DB
- func ResolveResource[ModelType any](model *ModelType) (resType string, resValues *opa.ResourceValues, err error)
- func SkipFiltering() func(*gorm.DB) *gorm.DB
- type DBOperationFlag
- type Filter
- type FilteredModel
- type GormMapperConfig
- type GormPartialQueryMapper
- func (m *GormPartialQueryMapper) And(_ context.Context, exprs ...clause.Expression) clause.Expression
- func (m *GormPartialQueryMapper) Comparison(ctx context.Context, op ast.Ref, colRef ast.Ref, val interface{}) (ret clause.Expression, err error)
- func (m *GormPartialQueryMapper) Context() context.Context
- func (m *GormPartialQueryMapper) MapResults(pq *rego.PartialQueries) (interface{}, error)
- func (m *GormPartialQueryMapper) Negate(_ context.Context, expr clause.Expression) clause.Expression
- func (m *GormPartialQueryMapper) Or(_ context.Context, exprs ...clause.Expression) clause.Expression
- func (m *GormPartialQueryMapper) Quote(field interface{}) string
- func (m *GormPartialQueryMapper) ResolveColumnExpr(_ context.Context, field *TaggedField, paths ...string) string
- func (m *GormPartialQueryMapper) ResolveField(_ context.Context, colRef ast.Ref) (ret *TaggedField, jsonbPath []string, err error)
- func (m *GormPartialQueryMapper) ResolveValueExpr(_ context.Context, val interface{}, field *TaggedField) interface{}
- func (m *GormPartialQueryMapper) ResultToJSON(result interface{}) (interface{}, error)
- func (m *GormPartialQueryMapper) WithContext(ctx context.Context) sdk.PartialQueryMapper
- type Metadata
- type OPATag
- type TaggedField
- type TaggedRelationPath
- type TaggedRelationship
Constants ¶
const ( DefaultQueryTemplate = `allow_%s` DefaultPartialQueryTemplate = `filter_%s` )
const ( TagOPA = `opa` TagDelimiter = `,` TagAssignment = `:` TagValueIgnore = "-" TagKeyInputField = `field` TagKeyInputFieldAlt = `input` TagKeyResourceType = `type` TagKeyOPAPackage = `package` )
Variables ¶
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 ¶
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
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 ¶
CreateClauses implements schema.CreateClausesInterface,
func (Filter) DeleteClauses ¶
DeleteClauses implements schema.DeleteClausesInterface,
func (Filter) QueryClauses ¶
QueryClauses implements schema.QueryClausesInterface,
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 (m *GormPartialQueryMapper) And(_ context.Context, exprs ...clause.Expression) clause.Expression
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 (m *GormPartialQueryMapper) Negate(_ context.Context, expr clause.Expression) clause.Expression
func (*GormPartialQueryMapper) Or ¶
func (m *GormPartialQueryMapper) Or(_ context.Context, exprs ...clause.Expression) clause.Expression
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 ¶
func (m *GormPartialQueryMapper) WithContext(ctx context.Context) sdk.PartialQueryMapper
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 ¶
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 }