From 69602148dc64e6a912b61bf82cb9da3ef1826694 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Mon, 5 Aug 2019 18:09:29 +0200 Subject: [PATCH] Add ability to precheck (in db query) access on resources --- internal/permissions/filter.go | 88 +++++++++++++++++++++ internal/permissions/permissions.go | 11 +++ internal/permissions/ruleset_checks_test.go | 20 +++++ 3 files changed, 119 insertions(+) create mode 100644 internal/permissions/filter.go diff --git a/internal/permissions/filter.go b/internal/permissions/filter.go new file mode 100644 index 000000000..1a25924c8 --- /dev/null +++ b/internal/permissions/filter.go @@ -0,0 +1,88 @@ +package permissions + +import ( + "fmt" + "strings" +) + +type ( + AccessCheck struct { + prefix string + pkColName string + + resource Resource + operation Operation + roles []uint64 + checkExplicitDeny bool + } +) + +func InitAccessCheckFilter(operation Operation, roles []uint64, checkExplicitDeny bool) AccessCheck { + var ac = AccessCheck{ + operation: operation, + roles: roles, + checkExplicitDeny: checkExplicitDeny, + pkColName: "id", + } + + return ac +} + +func (ac *AccessCheck) BindToEnv(resource Resource, prefix string) *AccessCheck { + ac.resource = resource + ac.prefix = prefix + return ac +} + +func (ac *AccessCheck) SetPrimaryKeyName(col string) *AccessCheck { + ac.pkColName = col + return ac +} + +// ToSql converts access check to SQL (with args) that will help with filtering +// +// Satisfies squirrel.Sqlizer interface +func (ac AccessCheck) ToSql() (sql string, args []interface{}, err error) { + if len(ac.roles) == 0 { + sql = "false" + return + } + + sql = fmt.Sprintf( + `EXISTS (SELECT 1 + FROM %s_permission_rules + WHERE resource = CONCAT(?, %s) + AND operation = ? + AND access = ? + AND rel_role IN (%s))`, + + ac.prefix, + ac.pkColName, + + // Generate placeholder for every role we have + strings.Repeat(",?", len(ac.roles))[1:], + ) + + args = []interface{}{ + ac.resource, + ac.operation, + } + + if ac.checkExplicitDeny { + // User has permissions to read on wildcard (*) resource + // so we need to check if there is any explicit denies + args = append(args, Deny) + sql = fmt.Sprintf("NOT %s", sql) + } else { + // User is explicitly denied to read on wildcard (*) resource + // check for all that have explicit allow + args = append(args, Allow) + + } + + for _, roleID := range ac.roles { + args = append(args, roleID) + } + + return sql, args, nil +} diff --git a/internal/permissions/permissions.go b/internal/permissions/permissions.go index d24026fe0..4b9cef069 100644 --- a/internal/permissions/permissions.go +++ b/internal/permissions/permissions.go @@ -42,6 +42,17 @@ func (a Access) String() string { } } +// Bool convers boolean true to Allow and false to Deny +func BoolToCheckFunc(isTrue bool) CheckAccessFunc { + return func() Access { + if isTrue { + return Allow + } + + return Deny + } +} + func (a *Access) UnmarshalJSON(data []byte) error { switch string(data) { case "allow": diff --git a/internal/permissions/ruleset_checks_test.go b/internal/permissions/ruleset_checks_test.go index f53977a6e..955b2df69 100644 --- a/internal/permissions/ruleset_checks_test.go +++ b/internal/permissions/ruleset_checks_test.go @@ -116,6 +116,26 @@ func TestRuleSet_checkResource(t *testing.T) { opAccess, Deny, }, + { // deny wc and and check if wc is denied + RuleSet{ + DenyRule(role1, resThingWc, opAccess), + AllowRule(role1, resThing42, opAccess), + }, + []uint64{role1}, + resThingWc, + opAccess, + Deny, + }, + { // allow wc and and check if wc is allowed + RuleSet{ + AllowRule(role1, resThingWc, opAccess), + DenyRule(role1, resThing42, opAccess), + }, + []uint64{role1}, + resThingWc, + opAccess, + Allow, + }, } )