3
0

Add filters to permissions for role route

It allows filtering for specific rules and also the rules which are applied to the resource, and not to a specific resource.

Introduces generic methods for RuleSet and FindRules method to access_control generation template.
This commit is contained in:
Vivek Patel
2022-07-06 11:17:49 +05:30
parent 149d75578a
commit f160d391f5
24 changed files with 712 additions and 14 deletions
+10
View File
@@ -276,6 +276,7 @@ endpoints:
- Client ID
- Session ID
imports:
- github.com/cortezaproject/corteza-server/pkg/filter
- github.com/cortezaproject/corteza-server/pkg/rbac
apis:
- name: list
@@ -318,6 +319,15 @@ endpoints:
type: uint64
required: true
title: Role ID
get:
- name: specific
required: false
title: Exclude (0, default), include (1) or return only (2) specific rules
type: "filter.State"
- name: resource
type: "[]string"
required: false
title: Show only rules for a specific resource
- name: delete
path: "/{roleID}/rules"
method: DELETE
+3 -2
View File
@@ -2,11 +2,11 @@ package rest
import (
"context"
"github.com/cortezaproject/corteza-server/automation/rest/request"
"github.com/cortezaproject/corteza-server/automation/service"
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/rbac"
)
@@ -20,6 +20,7 @@ type (
Trace(context.Context, uint64, []uint64, ...string) ([]*rbac.Trace, error)
List() []map[string]string
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
FindRules(ctx context.Context, roleID uint64, specific filter.State, rr ...string) (rbac.RuleSet, error)
Grant(ctx context.Context, rr ...*rbac.Rule) error
}
)
@@ -43,7 +44,7 @@ func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (i
}
func (ctrl Permissions) Read(ctx context.Context, r *request.PermissionsRead) (interface{}, error) {
return ctrl.ac.FindRulesByRoleID(ctx, r.RoleID)
return ctrl.ac.FindRules(ctx, r.RoleID, r.Specific, r.Resource...)
}
func (ctrl Permissions) Delete(ctx context.Context, r *request.PermissionsDelete) (interface{}, error) {
+47 -1
View File
@@ -11,6 +11,7 @@ package request
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/go-chi/chi/v5"
@@ -66,6 +67,16 @@ type (
//
// Role ID
RoleID uint64 `json:",string"`
// Specific GET parameter
//
// Exclude (0, default), include (1) or return only (2) specific rules
Specific filter.State
// Resource GET parameter
//
// Show only rules for a specific resource
Resource []string
}
PermissionsDelete struct {
@@ -216,7 +227,9 @@ func NewPermissionsRead() *PermissionsRead {
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) Auditable() map[string]interface{} {
return map[string]interface{}{
"roleID": r.RoleID,
"roleID": r.RoleID,
"specific": r.Specific,
"resource": r.Resource,
}
}
@@ -225,9 +238,42 @@ func (r PermissionsRead) GetRoleID() uint64 {
return r.RoleID
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetSpecific() filter.State {
return r.Specific
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetResource() []string {
return r.Resource
}
// Fill processes request and fills internal variables
func (r *PermissionsRead) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["specific"]; ok && len(val) > 0 {
r.Specific, err = payload.ParseFilterState(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["resource[]"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
} else if val, ok := tmp["resource"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
}
}
{
var val string
// path params
+58
View File
@@ -12,6 +12,7 @@ import (
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/rbac"
systemTypes "github.com/cortezaproject/corteza-server/system/types"
"github.com/spf13/cast"
@@ -259,6 +260,63 @@ func (svc accessControl) logGrants(ctx context.Context, rr []*rbac.Rule) {
}
}
// FindRules find all rules based on filters
//
// This function is auto-generated
func (svc accessControl) FindRules(ctx context.Context, roleID uint64, specific filter.State, rr ...string) (out rbac.RuleSet, err error) {
if !svc.CanGrant(ctx) {
return nil, AccessControlErrNotAllowedToSetPermissions()
}
rules, err := svc.FindRulesByRoleID(ctx, roleID)
if err != nil {
return
}
var (
resources []rbac.Resource
ruleMap = make(map[string]bool)
uniqRuleID = func(r *rbac.Rule) string {
return fmt.Sprintf("%s|%s|%d", r.Resource, r.Operation, r.RoleID)
}
)
// Filter based on resource
if len(rr) > 0 {
resources = make([]rbac.Resource, 0, len(rr))
for _, r := range rr {
if err = rbacResourceValidator(r); err != nil {
return nil, fmt.Errorf("can not use resource %q: %w", r, err)
}
resources = append(resources, rbac.NewResource(r))
}
} else {
resources = svc.Resources()
}
for _, res := range resources {
for _, rule := range rules.FilterResource(res.RbacResource()) {
if _, ok := ruleMap[uniqRuleID(rule)]; !ok {
out = append(out, rule)
ruleMap[uniqRuleID(rule)] = true
}
}
}
// Filter for Excluded, Include, or Exclusive specific rules
switch specific {
// Exclude all the specific rules
case filter.StateExcluded:
out = out.FilterRules(false)
// Returns only all the specific rules
case filter.StateExclusive:
out = out.FilterRules(true)
}
return
}
// FindRulesByRoleID find all rules for a specific role
//
// This function is auto-generated
@@ -9,6 +9,7 @@ import (
"context"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/filter"
systemTypes "github.com/cortezaproject/corteza-server/system/types"
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
{{- range .imports }}
@@ -204,6 +205,63 @@ func (svc accessControl) logGrants(ctx context.Context, rr []*rbac.Rule) {
}
}
// FindRules find all rules based on filters
//
// This function is auto-generated
func (svc accessControl) FindRules(ctx context.Context, roleID uint64, specific filter.State, rr ...string) (out rbac.RuleSet, err error) {
if !svc.CanGrant(ctx) {
return nil, AccessControlErrNotAllowedToSetPermissions()
}
rules, err := svc.FindRulesByRoleID(ctx, roleID)
if err != nil {
return
}
var (
resources []rbac.Resource
ruleMap = make(map[string]bool)
uniqRuleID = func(r *rbac.Rule) string {
return fmt.Sprintf("%s|%s|%d", r.Resource, r.Operation, r.RoleID)
}
)
// Filter based on resource
if len(rr) > 0 {
resources = make([]rbac.Resource, 0, len(rr))
for _, r := range rr {
if err = rbacResourceValidator(r); err != nil {
return nil, fmt.Errorf("can not use resource %q: %w", r, err)
}
resources = append(resources, rbac.NewResource(r))
}
} else {
resources = svc.Resources()
}
for _, res := range resources {
for _, rule := range rules.FilterResource(res.RbacResource()) {
if _, ok := ruleMap[uniqRuleID(rule)]; !ok {
out = append(out, rule)
ruleMap[uniqRuleID(rule)] = true
}
}
}
// Filter for Excluded, Include, or Exclusive specific rules
switch specific {
// Exclude all the specific rules
case filter.StateExcluded:
out = out.FilterRules(false)
// Returns only all the specific rules
case filter.StateExclusive:
out = out.FilterRules(true)
}
return
}
// FindRulesByRoleID find all rules for a specific role
//
// This function is auto-generated
+10
View File
@@ -1291,6 +1291,7 @@ endpoints:
- Client ID
- Session ID
imports:
- github.com/cortezaproject/corteza-server/pkg/filter
- github.com/cortezaproject/corteza-server/pkg/rbac
apis:
- name: list
@@ -1333,6 +1334,15 @@ endpoints:
type: uint64
required: true
title: Role ID
get:
- name: specific
required: false
title: Exclude (0, default), include (1) or return only (2) specific rules
type: "filter.State"
- name: resource
type: "[]string"
required: false
title: Show only rules for a specific resource
- name: delete
path: "/{roleID}/rules"
method: DELETE
+3 -2
View File
@@ -2,11 +2,11 @@ package rest
import (
"context"
"github.com/cortezaproject/corteza-server/compose/rest/request"
"github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/rbac"
)
@@ -20,6 +20,7 @@ type (
Trace(context.Context, uint64, []uint64, ...string) ([]*rbac.Trace, error)
List() []map[string]string
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
FindRules(ctx context.Context, roleID uint64, specific filter.State, rr ...string) (rbac.RuleSet, error)
Grant(ctx context.Context, rr ...*rbac.Rule) error
}
)
@@ -43,7 +44,7 @@ func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (i
}
func (ctrl Permissions) Read(ctx context.Context, r *request.PermissionsRead) (interface{}, error) {
return ctrl.ac.FindRulesByRoleID(ctx, r.RoleID)
return ctrl.ac.FindRules(ctx, r.RoleID, r.Specific, r.Resource...)
}
func (ctrl Permissions) Delete(ctx context.Context, r *request.PermissionsDelete) (interface{}, error) {
+47 -1
View File
@@ -11,6 +11,7 @@ package request
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/go-chi/chi/v5"
@@ -66,6 +67,16 @@ type (
//
// Role ID
RoleID uint64 `json:",string"`
// Specific GET parameter
//
// Exclude (0, default), include (1) or return only (2) specific rules
Specific filter.State
// Resource GET parameter
//
// Show only rules for a specific resource
Resource []string
}
PermissionsDelete struct {
@@ -216,7 +227,9 @@ func NewPermissionsRead() *PermissionsRead {
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) Auditable() map[string]interface{} {
return map[string]interface{}{
"roleID": r.RoleID,
"roleID": r.RoleID,
"specific": r.Specific,
"resource": r.Resource,
}
}
@@ -225,9 +238,42 @@ func (r PermissionsRead) GetRoleID() uint64 {
return r.RoleID
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetSpecific() filter.State {
return r.Specific
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetResource() []string {
return r.Resource
}
// Fill processes request and fills internal variables
func (r *PermissionsRead) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["specific"]; ok && len(val) > 0 {
r.Specific, err = payload.ParseFilterState(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["resource[]"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
} else if val, ok := tmp["resource"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
}
}
{
var val string
// path params
+58
View File
@@ -12,6 +12,7 @@ import (
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/rbac"
systemTypes "github.com/cortezaproject/corteza-server/system/types"
"github.com/spf13/cast"
@@ -369,6 +370,63 @@ func (svc accessControl) logGrants(ctx context.Context, rr []*rbac.Rule) {
}
}
// FindRules find all rules based on filters
//
// This function is auto-generated
func (svc accessControl) FindRules(ctx context.Context, roleID uint64, specific filter.State, rr ...string) (out rbac.RuleSet, err error) {
if !svc.CanGrant(ctx) {
return nil, AccessControlErrNotAllowedToSetPermissions()
}
rules, err := svc.FindRulesByRoleID(ctx, roleID)
if err != nil {
return
}
var (
resources []rbac.Resource
ruleMap = make(map[string]bool)
uniqRuleID = func(r *rbac.Rule) string {
return fmt.Sprintf("%s|%s|%d", r.Resource, r.Operation, r.RoleID)
}
)
// Filter based on resource
if len(rr) > 0 {
resources = make([]rbac.Resource, 0, len(rr))
for _, r := range rr {
if err = rbacResourceValidator(r); err != nil {
return nil, fmt.Errorf("can not use resource %q: %w", r, err)
}
resources = append(resources, rbac.NewResource(r))
}
} else {
resources = svc.Resources()
}
for _, res := range resources {
for _, rule := range rules.FilterResource(res.RbacResource()) {
if _, ok := ruleMap[uniqRuleID(rule)]; !ok {
out = append(out, rule)
ruleMap[uniqRuleID(rule)] = true
}
}
}
// Filter for Excluded, Include, or Exclusive specific rules
switch specific {
// Exclude all the specific rules
case filter.StateExcluded:
out = out.FilterRules(false)
// Returns only all the specific rules
case filter.StateExclusive:
out = out.FilterRules(true)
}
return
}
// FindRulesByRoleID find all rules for a specific role
//
// This function is auto-generated
+10
View File
@@ -503,6 +503,7 @@ endpoints:
- Client ID
- Session ID
imports:
- github.com/cortezaproject/corteza-server/pkg/filter
- github.com/cortezaproject/corteza-server/pkg/rbac
apis:
- name: list
@@ -545,6 +546,15 @@ endpoints:
type: uint64
required: true
title: Role ID
get:
- name: specific
required: false
title: Exclude (0, default), include (1) or return only (2) specific rules
type: "filter.State"
- name: resource
type: "[]string"
required: false
title: Show only rules for a specific resource
- name: delete
path: "/{roleID}/rules"
method: DELETE
+3 -2
View File
@@ -2,11 +2,11 @@ package rest
import (
"context"
"github.com/cortezaproject/corteza-server/federation/rest/request"
"github.com/cortezaproject/corteza-server/federation/service"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/rbac"
)
@@ -20,6 +20,7 @@ type (
Trace(context.Context, uint64, []uint64, ...string) ([]*rbac.Trace, error)
List() []map[string]string
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
FindRules(ctx context.Context, roleID uint64, specific filter.State, rr ...string) (rbac.RuleSet, error)
Grant(ctx context.Context, rr ...*rbac.Rule) error
}
)
@@ -43,7 +44,7 @@ func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (i
}
func (ctrl Permissions) Read(ctx context.Context, r *request.PermissionsRead) (interface{}, error) {
return ctrl.ac.FindRulesByRoleID(ctx, r.RoleID)
return ctrl.ac.FindRules(ctx, r.RoleID, r.Specific, r.Resource...)
}
func (ctrl Permissions) Delete(ctx context.Context, r *request.PermissionsDelete) (interface{}, error) {
+47 -1
View File
@@ -11,6 +11,7 @@ package request
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/go-chi/chi/v5"
@@ -66,6 +67,16 @@ type (
//
// Role ID
RoleID uint64 `json:",string"`
// Specific GET parameter
//
// Exclude (0, default), include (1) or return only (2) specific rules
Specific filter.State
// Resource GET parameter
//
// Show only rules for a specific resource
Resource []string
}
PermissionsDelete struct {
@@ -216,7 +227,9 @@ func NewPermissionsRead() *PermissionsRead {
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) Auditable() map[string]interface{} {
return map[string]interface{}{
"roleID": r.RoleID,
"roleID": r.RoleID,
"specific": r.Specific,
"resource": r.Resource,
}
}
@@ -225,9 +238,42 @@ func (r PermissionsRead) GetRoleID() uint64 {
return r.RoleID
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetSpecific() filter.State {
return r.Specific
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetResource() []string {
return r.Resource
}
// Fill processes request and fills internal variables
func (r *PermissionsRead) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["specific"]; ok && len(val) > 0 {
r.Specific, err = payload.ParseFilterState(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["resource[]"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
} else if val, ok := tmp["resource"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
}
}
{
var val string
// path params
+58
View File
@@ -12,6 +12,7 @@ import (
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/rbac"
systemTypes "github.com/cortezaproject/corteza-server/system/types"
"github.com/spf13/cast"
@@ -246,6 +247,63 @@ func (svc accessControl) logGrants(ctx context.Context, rr []*rbac.Rule) {
}
}
// FindRules find all rules based on filters
//
// This function is auto-generated
func (svc accessControl) FindRules(ctx context.Context, roleID uint64, specific filter.State, rr ...string) (out rbac.RuleSet, err error) {
if !svc.CanGrant(ctx) {
return nil, AccessControlErrNotAllowedToSetPermissions()
}
rules, err := svc.FindRulesByRoleID(ctx, roleID)
if err != nil {
return
}
var (
resources []rbac.Resource
ruleMap = make(map[string]bool)
uniqRuleID = func(r *rbac.Rule) string {
return fmt.Sprintf("%s|%s|%d", r.Resource, r.Operation, r.RoleID)
}
)
// Filter based on resource
if len(rr) > 0 {
resources = make([]rbac.Resource, 0, len(rr))
for _, r := range rr {
if err = rbacResourceValidator(r); err != nil {
return nil, fmt.Errorf("can not use resource %q: %w", r, err)
}
resources = append(resources, rbac.NewResource(r))
}
} else {
resources = svc.Resources()
}
for _, res := range resources {
for _, rule := range rules.FilterResource(res.RbacResource()) {
if _, ok := ruleMap[uniqRuleID(rule)]; !ok {
out = append(out, rule)
ruleMap[uniqRuleID(rule)] = true
}
}
}
// Filter for Excluded, Include, or Exclusive specific rules
switch specific {
// Exclude all the specific rules
case filter.StateExcluded:
out = out.FilterRules(false)
// Returns only all the specific rules
case filter.StateExclusive:
out = out.FilterRules(true)
}
return
}
// FindRulesByRoleID find all rules for a specific role
//
// This function is auto-generated
+2
View File
@@ -220,6 +220,8 @@ func (d *restEndpointParamDef) Parser(arg string) string {
return fmt.Sprintf("payload.Parse%s(%s), nil", export(d.Type), arg)
case "string", "[]string":
return fmt.Sprintf("%s, nil", arg)
case "filter.State":
return fmt.Sprintf("payload.ParseFilterState(%s), nil", arg)
default:
return fmt.Sprintf("%s(%s), nil", d.Type, arg)
}
+5
View File
@@ -2,6 +2,7 @@ package payload
import (
"fmt"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/jmoiron/sqlx/types"
"github.com/spf13/cast"
"strconv"
@@ -100,3 +101,7 @@ func ParseInt64(s string) int64 {
func ParseBool(s string) bool {
return cast.ToBool(s)
}
func ParseFilterState(s string) filter.State {
return filter.State(cast.ToUint(s))
}
+50
View File
@@ -0,0 +1,50 @@
package payload
import (
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/stretchr/testify/require"
"testing"
)
func TestParseFilterState(t *testing.T) {
var (
req = require.New(t)
)
tests := []struct {
name string
input string
expected filter.State
}{
{
"invalid string should be Excluded",
"zero",
filter.StateExcluded,
},
{
"empty string should be Excluded",
"0",
filter.StateExcluded,
},
{
"Excluded",
"0",
filter.StateExcluded,
},
{
"Excluded",
"1",
filter.StateInclusive,
},
{
"Excluded",
"2",
filter.StateExclusive,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req.Equal(tt.expected, ParseFilterState(tt.input))
})
}
}
+26
View File
@@ -102,3 +102,29 @@ func level(r string) (score int) {
return
}
// isSpecific will return true if rule relates to a specific resource
//
// ns::cmp:res/xx/* returns true
// ns::cmp:res/xx/xx returns true
// ns::cmp:res/ returns false
// ns::cmp:res/*/* returns false
func isSpecific(r string) (out bool) {
out = false
parts := strings.Split(r, pathSep)
// remove resource name part
parts = parts[1:]
// check parts for wildcard if it's not empty otherwise return false
for _, p := range parts {
if len(p) == 0 {
continue
}
if p != wildcard && !out {
out = true
}
}
return
}
+24
View File
@@ -115,3 +115,27 @@ func TestLevel(t *testing.T) {
})
}
}
func TestIsSpecific(t *testing.T) {
var (
tcc = []struct {
r string
e bool
}{
{"corteza::test/a/b/c", true},
{"corteza::test/a/b/*", true},
{"corteza::test/a/*/*", true},
{"corteza::test/a/*/123", true},
{"corteza::test/*/*/*", false},
{"corteza::test/*/*", false},
{"corteza::test/*", false},
{"corteza::test/", false},
}
)
for _, tc := range tcc {
t.Run(tc.r, func(t *testing.T) {
require.Equal(t, tc.e, isSpecific(tc.r))
})
}
}
+13 -1
View File
@@ -62,7 +62,7 @@ func (set RuleSet) FilterAccess(a Access) (out RuleSet) {
func (set RuleSet) FilterResource(res string) (out RuleSet) {
for _, r := range set {
if !matchResource(r.Resource, res) {
if !matchResource(res, r.Resource) {
continue
}
out = append(out, r)
@@ -71,6 +71,18 @@ func (set RuleSet) FilterResource(res string) (out RuleSet) {
return
}
// FilterRules will filter the rules based on given parameter(specific),
// If params is true then it will return only the specific rules otherwise it will return non-specific rules
func (set RuleSet) FilterRules(specific bool) (out RuleSet) {
for _, r := range set {
if specific == isSpecific(r.Resource) {
out = append(out, r)
}
}
return
}
// AllowRule helper func to create allow rule
func AllowRule(id uint64, r, o string) *Rule {
return &Rule{id, r, o, Allow, false}
+10
View File
@@ -1219,6 +1219,7 @@ endpoints:
- Client ID
- Session ID
imports:
- github.com/cortezaproject/corteza-server/pkg/filter
- github.com/cortezaproject/corteza-server/pkg/rbac
apis:
- name: list
@@ -1262,6 +1263,15 @@ endpoints:
type: uint64
required: true
title: Role ID
get:
- name: specific
required: false
title: Exclude (0, default), include (1) or return only (2) specific rules
type: "filter.State"
- name: resource
type: "[]string"
required: false
title: Show only rules for a specific resource
- name: delete
path: "/{roleID}/rules"
method: DELETE
+3 -2
View File
@@ -2,8 +2,8 @@ package rest
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/system/rest/request"
"github.com/cortezaproject/corteza-server/system/service"
@@ -20,6 +20,7 @@ type (
Trace(context.Context, uint64, []uint64, ...string) ([]*rbac.Trace, error)
List() []map[string]string
FindRulesByRoleID(context.Context, uint64) (rbac.RuleSet, error)
FindRules(ctx context.Context, roleID uint64, specific filter.State, rr ...string) (rbac.RuleSet, error)
Grant(ctx context.Context, rr ...*rbac.Rule) error
}
)
@@ -43,7 +44,7 @@ func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (i
}
func (ctrl Permissions) Read(ctx context.Context, r *request.PermissionsRead) (interface{}, error) {
return ctrl.ac.FindRulesByRoleID(ctx, r.RoleID)
return ctrl.ac.FindRules(ctx, r.RoleID, r.Specific, r.Resource...)
}
func (ctrl Permissions) Delete(ctx context.Context, r *request.PermissionsDelete) (interface{}, error) {
+48 -1
View File
@@ -11,6 +11,7 @@ package request
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/go-chi/chi/v5"
@@ -66,6 +67,16 @@ type (
//
// Role ID
RoleID uint64 `json:",string"`
// Specific GET parameter
//
// Exclude (0, default), include (1) or return only (2) specific rules
Specific filter.State
// Resource GET parameter
//
// Show only rules for a specific resource
Resource []string
}
PermissionsDelete struct {
@@ -216,7 +227,9 @@ func NewPermissionsRead() *PermissionsRead {
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) Auditable() map[string]interface{} {
return map[string]interface{}{
"roleID": r.RoleID,
"roleID": r.RoleID,
"specific": r.Specific,
"resource": r.Resource,
}
}
@@ -225,9 +238,43 @@ func (r PermissionsRead) GetRoleID() uint64 {
return r.RoleID
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetSpecific() filter.State {
return r.Specific
}
// Auditable returns all auditable/loggable parameters
func (r PermissionsRead) GetResource() []string {
return r.Resource
}
// Fill processes request and fills internal variables
func (r *PermissionsRead) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["specific"]; ok && len(val) > 0 {
r.Specific, err = payload.ParseFilterState(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["resource[]"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
} else if val, ok := tmp["resource"]; ok {
r.Resource, err = val, nil
if err != nil {
return err
}
}
}
{
var val string
// path params
+58
View File
@@ -11,6 +11,7 @@ import (
"fmt"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
internalAuth "github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/system/types"
systemTypes "github.com/cortezaproject/corteza-server/system/types"
@@ -576,6 +577,63 @@ func (svc accessControl) logGrants(ctx context.Context, rr []*rbac.Rule) {
}
}
// FindRules find all rules based on filters
//
// This function is auto-generated
func (svc accessControl) FindRules(ctx context.Context, roleID uint64, specific filter.State, rr ...string) (out rbac.RuleSet, err error) {
if !svc.CanGrant(ctx) {
return nil, AccessControlErrNotAllowedToSetPermissions()
}
rules, err := svc.FindRulesByRoleID(ctx, roleID)
if err != nil {
return
}
var (
resources []rbac.Resource
ruleMap = make(map[string]bool)
uniqRuleID = func(r *rbac.Rule) string {
return fmt.Sprintf("%s|%s|%d", r.Resource, r.Operation, r.RoleID)
}
)
// Filter based on resource
if len(rr) > 0 {
resources = make([]rbac.Resource, 0, len(rr))
for _, r := range rr {
if err = rbacResourceValidator(r); err != nil {
return nil, fmt.Errorf("can not use resource %q: %w", r, err)
}
resources = append(resources, rbac.NewResource(r))
}
} else {
resources = svc.Resources()
}
for _, res := range resources {
for _, rule := range rules.FilterResource(res.RbacResource()) {
if _, ok := ruleMap[uniqRuleID(rule)]; !ok {
out = append(out, rule)
ruleMap[uniqRuleID(rule)] = true
}
}
}
// Filter for Excluded, Include, or Exclusive specific rules
switch specific {
// Exclude all the specific rules
case filter.StateExcluded:
out = out.FilterRules(false)
// Returns only all the specific rules
case filter.StateExclusive:
out = out.FilterRules(true)
}
return
}
// FindRulesByRoleID find all rules for a specific role
//
// This function is auto-generated
+61 -1
View File
@@ -2,6 +2,7 @@ package system
import (
"fmt"
"github.com/cortezaproject/corteza-server/pkg/id"
"net/http"
"strconv"
"testing"
@@ -30,7 +31,7 @@ func TestPermissionsList(t *testing.T) {
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
h.apiInit().
json := h.apiInit().
Get("/permissions/").
Header("Accept", "application/json").
Expect(t).
@@ -38,6 +39,8 @@ func TestPermissionsList(t *testing.T) {
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Present(fmt.Sprintf(`$.response[? @.type=="%s"]`, types.ComponentResourceType))).
End()
fmt.Println("json: ", json.Response.Body)
}
func TestPermissionsRead(t *testing.T) {
@@ -54,6 +57,63 @@ func TestPermissionsRead(t *testing.T) {
End()
}
func TestPermissionsReadWithFilter(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
helpers.DenyMe(h, types.ComponentRbacResource(), "user.create")
// Specific resource related rules
testID := id.Next()
helpers.AllowMe(h, types.UserRbacResource(testID), "read")
helpers.AllowMe(h, types.UserRbacResource(testID), "update")
// Only non-specific resource rules with `specific: 0` filter
h.apiInit().
Getf("/permissions/%d/rules", h.roleID).
Query("specific", "0").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response`, 2)).
End()
// Including all specific and non-specific resource rules with `specific: 1` filter
h.apiInit().
Getf("/permissions/%d/rules", h.roleID).
Query("specific", "1").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response`, 4)).
End()
// Only specific resource rules with `specific: 2` filter
h.apiInit().
Getf("/permissions/%d/rules", h.roleID).
Query("specific", "2").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response`, 2)).
End()
// Only resource related rules with `resource: corteza::system:user/{ID}`
h.apiInit().
Getf("/permissions/%d/rules", h.roleID).
Query("specific", "1").
Query("resource", fmt.Sprintf("corteza::system:user/%d", testID)).
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
Assert(jsonpath.Len(`$.response`, 2)).
End()
}
func TestPermissionsUpdate(t *testing.T) {
h := newHelper(t)
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")