Moved groups to use SubGroups instead so one node can support many sub_groups(admin, ref, user, etc.) to reduce signals sent

gql_cataclysm
noah metz 2023-10-15 15:14:33 -06:00
parent 0159d0dd5a
commit 4b7bc93914
10 changed files with 425 additions and 188 deletions

@ -4,6 +4,7 @@ import (
"testing" "testing"
"time" "time"
"reflect" "reflect"
"runtime/debug"
) )
func checkSignal[S Signal](t *testing.T, signal Signal, check func(S)){ func checkSignal[S Signal](t *testing.T, signal Signal, check func(S)){
@ -11,6 +12,7 @@ func checkSignal[S Signal](t *testing.T, signal Signal, check func(S)){
if cast_ok == false { if cast_ok == false {
error_signal, is_error := signal.(*ErrorSignal) error_signal, is_error := signal.(*ErrorSignal)
if is_error { if is_error {
t.Log(string(debug.Stack()))
t.Fatal(error_signal.Error) t.Fatal(error_signal.Error)
} }
t.Fatalf("Response of wrong type %s", reflect.TypeOf(signal)) t.Fatalf("Response of wrong type %s", reflect.TypeOf(signal))
@ -54,7 +56,7 @@ func testSend(t *testing.T, ctx *Context, signal Signal, source, destination *No
} }
func TestACLBasic(t *testing.T) { func TestACLBasic(t *testing.T) {
ctx := logTestContext(t, []string{"test", "acl"}) ctx := logTestContext(t, []string{"test", "acl", "policy"})
listener, err := NewNode(ctx, nil, BaseNodeType, 100, nil, NewListenerExt(100)) listener, err := NewNode(ctx, nil, BaseNodeType, 100, nil, NewListenerExt(100))
fatalErr(t, err) fatalErr(t, err)
@ -68,24 +70,33 @@ func TestACLBasic(t *testing.T) {
NewPerNodePolicy(map[NodeID]Tree{ NewPerNodePolicy(map[NodeID]Tree{
listener.ID: { listener.ID: {
SerializedType(AddMemberSignalType): nil, SerializedType(AddMemberSignalType): nil,
SerializedType(AddSubGroupSignalType): nil,
}, },
}), }),
}, NewGroupExt(nil)) }, NewGroupExt(nil))
fatalErr(t, err) fatalErr(t, err)
testSendACL(t, ctx, listener, nil, []Policy{ testSendACL(t, ctx, listener, nil, []Policy{
NewMemberOfPolicy(map[NodeID]Tree{ NewMemberOfPolicy(map[NodeID]map[string]Tree{
group.ID: nil, group.ID: {
"test_group": nil,
},
}), }),
}, testErrorSignal(t, "acl_denied")) }, testErrorSignal(t, "acl_denied"))
add_member_signal := NewAddMemberSignal(listener.ID) add_subgroup_signal := NewAddSubGroupSignal("test_group")
add_subgroup_response := testSend(t, ctx, add_subgroup_signal, listener, group)
checkSignal(t, add_subgroup_response, testSuccess)
add_member_signal := NewAddMemberSignal("test_group", listener.ID)
add_member_response := testSend(t, ctx, add_member_signal, listener, group) add_member_response := testSend(t, ctx, add_member_signal, listener, group)
checkSignal(t, add_member_response, testSuccess) checkSignal(t, add_member_response, testSuccess)
testSendACL(t, ctx, listener, nil, []Policy{ testSendACL(t, ctx, listener, nil, []Policy{
NewMemberOfPolicy(map[NodeID]Tree{ NewMemberOfPolicy(map[NodeID]map[string]Tree{
group.ID: nil, group.ID: {
"test_group": nil,
},
}), }),
}, testSuccess) }, testSuccess)
@ -107,7 +118,15 @@ func TestACLBasic(t *testing.T) {
NewACLProxyPolicy([]NodeID{acl_proxy_2.ID}), NewACLProxyPolicy([]NodeID{acl_proxy_2.ID}),
}, testSuccess) }, testSuccess)
acl_proxy_3, err := NewNode(ctx, nil, BaseNodeType, 100, []Policy{DefaultACLPolicy}, NewACLExt([]Policy{NewMemberOfPolicy(map[NodeID]Tree{group.ID: nil})})) acl_proxy_3, err := NewNode(ctx, nil, BaseNodeType, 100, []Policy{DefaultACLPolicy},
NewACLExt([]Policy{
NewMemberOfPolicy(map[NodeID]map[string]Tree{
group.ID: {
"test_group": nil,
},
}),
}),
)
fatalErr(t, err) fatalErr(t, err)
testSendACL(t, ctx, listener, nil, []Policy{ testSendACL(t, ctx, listener, nil, []Policy{

@ -1304,6 +1304,16 @@ func NewContext(db * badger.DB, log Logger) (*Context, error) {
return nil, err return nil, err
} }
err = ctx.RegisterSignal(reflect.TypeOf(AddSubGroupSignal{}), AddSubGroupSignalType)
if err != nil {
return nil, err
}
err = ctx.RegisterSignal(reflect.TypeOf(RemoveSubGroupSignal{}), RemoveSubGroupSignalType)
if err != nil {
return nil, err
}
err = ctx.RegisterSignal(reflect.TypeOf(ACLTimeoutSignal{}), ACLTimeoutSignalType) err = ctx.RegisterSignal(reflect.TypeOf(ACLTimeoutSignal{}), ACLTimeoutSignalType)
if err != nil { if err != nil {
return nil, err return nil, err

@ -1176,25 +1176,71 @@ func NewGQLExtContext() *GQLExtContext {
panic(err) panic(err)
} }
err = context.RegisterField(context.Interfaces["Node"].List, "Members", GroupExtType, "members", sub_group_type := graphql.NewObject(graphql.ObjectConfig{
func(p graphql.ResolveParams, ctx *ResolveContext, value reflect.Value)(interface{}, error) { Name: "SubGroup",
node_list, ok := value.Interface().([]NodeID) Interfaces: nil,
Fields: graphql.Fields{
"Name": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
val, ok := p.Source.(SubGroupGQL)
if ok == false {
return nil, fmt.Errorf("WRONG_TYPE_RETURNED")
}
return val.Name, nil
},
},
"Members": &graphql.Field{
Type: context.Interfaces["Node"].List,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
ctx, err := PrepResolve(p)
if err != nil {
return nil, err
}
val, ok := p.Source.(SubGroupGQL)
if ok == false { if ok == false {
return nil, fmt.Errorf("value is %+v, not []NodeID", value.Type()) return nil, fmt.Errorf("WRONG_TYPE_RETURNED")
} }
nodes, err := ResolveNodes(ctx, p, node_list) nodes, err := ResolveNodes(ctx, p, val.Members)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return nodes, nil return nodes, nil
},
},
},
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
return reflect.TypeOf(p.Value) == reflect.TypeOf(SubGroupGQL{})
},
Description: "SubGroup within Group",
})
context.Types = append(context.Types, sub_group_type)
err = context.RegisterField(sub_group_type, "SubGroups", GroupExtType, "sub_groups",
func(p graphql.ResolveParams, ctx *ResolveContext, value reflect.Value)(interface{}, error) {
node_map, ok := value.Interface().(map[string]SubGroup)
if ok == false {
return nil, fmt.Errorf("value is %+v, not map[string]SubGroup", value.Type())
}
sub_groups := []SubGroupGQL{}
for name, sub_group := range(node_map) {
sub_groups = append(sub_groups, SubGroupGQL{
name,
sub_group.Members,
})
}
return sub_groups, nil
}) })
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = context.RegisterInterface("Group", "DefaultGroup", []string{"Node"}, []string{"Members"}, map[string]SelfField{}, map[string]ListField{}) err = context.RegisterInterface("Group", "DefaultGroup", []string{"Node"}, []string{"SubGroups"}, map[string]SelfField{}, map[string]ListField{})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -1242,7 +1288,7 @@ func NewGQLExtContext() *GQLExtContext {
panic(err) panic(err)
} }
err = context.RegisterNodeType(GQLNodeType, "GQLServer", []string{"Node", "Lockable", "Group"}, []string{"Listen", "Owner", "Requirements", "Members"}) err = context.RegisterNodeType(GQLNodeType, "GQLServer", []string{"Node", "Lockable", "Group"}, []string{"Listen", "Owner", "Requirements", "SubGroups"})
if err != nil { if err != nil {
panic(err) panic(err)
} }

@ -63,13 +63,15 @@ func TestGQLServer(t *testing.T) {
SerializedType(ErrorSignalType): nil, SerializedType(ErrorSignalType): nil,
}) })
group_policy_2 := NewMemberOfPolicy(map[NodeID]Tree{ group_policy_2 := NewMemberOfPolicy(map[NodeID]map[string]Tree{
gql_id: { gql_id: {
"test_group": {
SerializedType(LinkSignalType): nil, SerializedType(LinkSignalType): nil,
SerializedType(LockSignalType): nil, SerializedType(LockSignalType): nil,
SerializedType(StatusSignalType): nil, SerializedType(StatusSignalType): nil,
SerializedType(ReadSignalType): nil, SerializedType(ReadSignalType): nil,
}, },
},
}) })
user_policy_1 := NewAllNodesPolicy(Tree{ user_policy_1 := NewAllNodesPolicy(Tree{
@ -77,12 +79,14 @@ func TestGQLServer(t *testing.T) {
SerializedType(ErrorSignalType): nil, SerializedType(ErrorSignalType): nil,
}) })
user_policy_2 := NewMemberOfPolicy(map[NodeID]Tree{ user_policy_2 := NewMemberOfPolicy(map[NodeID]map[string]Tree{
gql_id: { gql_id: {
"test_group": {
SerializedType(LinkSignalType): nil, SerializedType(LinkSignalType): nil,
SerializedType(ReadSignalType): nil, SerializedType(ReadSignalType): nil,
SerializedType(LockSignalType): nil, SerializedType(LockSignalType): nil,
}, },
},
}) })
gql_ext, err := NewGQLExt(ctx, ":0", nil, nil) gql_ext, err := NewGQLExt(ctx, ":0", nil, nil)
@ -93,7 +97,7 @@ func TestGQLServer(t *testing.T) {
fatalErr(t, err) fatalErr(t, err)
gql, err := NewNode(ctx, gql_key, GQLNodeType, 10, []Policy{group_policy_2, group_policy_1}, gql, err := NewNode(ctx, gql_key, GQLNodeType, 10, []Policy{group_policy_2, group_policy_1},
NewLockableExt([]NodeID{n1.ID}), gql_ext, NewGroupExt([]NodeID{n1.ID, gql_id}), listener_ext) NewLockableExt([]NodeID{n1.ID}), gql_ext, NewGroupExt(map[string][]NodeID{"test_group": {n1.ID, gql_id}}), listener_ext)
fatalErr(t, err) fatalErr(t, err)
ctx.Log.Logf("test", "GQL: %s", gql.ID) ctx.Log.Logf("test", "GQL: %s", gql.ID)
@ -120,7 +124,7 @@ func TestGQLServer(t *testing.T) {
} }
req_2 := GQLPayload{ req_2 := GQLPayload{
Query: "query Node($id:String) { Node(id:$id) { ID, TypeHash, ... on GQLServer { Members { ID } , Listen, Requirements { ID, TypeHash Owner { ID } } } } }", Query: "query Node($id:String) { Node(id:$id) { ID, TypeHash, ... on GQLServer { SubGroups { Name, Members { ID } } , Listen, Requirements { ID, TypeHash Owner { ID } } } } }",
Variables: map[string]interface{}{ Variables: map[string]interface{}{
"id": gql.ID.String(), "id": gql.ID.String(),
}, },

@ -4,38 +4,91 @@ import (
"slices" "slices"
) )
type AddSubGroupSignal struct {
SignalHeader
Name string `gv:"name"`
}
func NewAddSubGroupSignal(name string) *AddSubGroupSignal {
return &AddSubGroupSignal{
NewSignalHeader(Direct),
name,
}
}
func (signal AddSubGroupSignal) Permission() Tree {
return Tree{
SerializedType(AddSubGroupSignalType): {
Hash("name", signal.Name): nil,
},
}
}
type RemoveSubGroupSignal struct {
SignalHeader
Name string `gv:"name"`
}
func NewRemoveSubGroupSignal(name string) *RemoveSubGroupSignal {
return &RemoveSubGroupSignal{
NewSignalHeader(Direct),
name,
}
}
func (signal RemoveSubGroupSignal) Permission() Tree {
return Tree{
SerializedType(RemoveSubGroupSignalType): {
Hash("command", signal.Name): nil,
},
}
}
type AddMemberSignal struct { type AddMemberSignal struct {
SignalHeader SignalHeader
SubGroup string `gv:"sub_group"`
MemberID NodeID `gv:"member_id"` MemberID NodeID `gv:"member_id"`
} }
type SubGroupGQL struct {
Name string
Members []NodeID
}
func (signal AddMemberSignal) Permission() Tree { func (signal AddMemberSignal) Permission() Tree {
return Tree{ return Tree{
SerializedType(AddMemberSignalType): nil, SerializedType(AddMemberSignalType): {
Hash("sub_group", signal.SubGroup): nil,
},
} }
} }
func NewAddMemberSignal(member_id NodeID) *AddMemberSignal { func NewAddMemberSignal(sub_group string, member_id NodeID) *AddMemberSignal {
return &AddMemberSignal{ return &AddMemberSignal{
NewSignalHeader(Direct), NewSignalHeader(Direct),
sub_group,
member_id, member_id,
} }
} }
type RemoveMemberSignal struct { type RemoveMemberSignal struct {
SignalHeader SignalHeader
SubGroup string `gv:"sub_group"`
MemberID NodeID `gv:"member_id"` MemberID NodeID `gv:"member_id"`
} }
func (signal RemoveMemberSignal) Permission() Tree { func (signal RemoveMemberSignal) Permission() Tree {
return Tree{ return Tree{
SerializedType(RemoveMemberSignalType): nil, SerializedType(RemoveMemberSignalType): {
Hash("sub_group", signal.SubGroup): nil,
},
} }
} }
func NewRemoveMemberSignal(member_id NodeID) *RemoveMemberSignal { func NewRemoveMemberSignal(sub_group string, member_id NodeID) *RemoveMemberSignal {
return &RemoveMemberSignal{ return &RemoveMemberSignal{
NewSignalHeader(Direct), NewSignalHeader(Direct),
sub_group,
member_id, member_id,
} }
} }
@ -43,18 +96,123 @@ func NewRemoveMemberSignal(member_id NodeID) *RemoveMemberSignal {
var DefaultGroupPolicy = NewAllNodesPolicy(Tree{ var DefaultGroupPolicy = NewAllNodesPolicy(Tree{
SerializedType(ReadSignalType): { SerializedType(ReadSignalType): {
SerializedType(GroupExtType): { SerializedType(GroupExtType): {
Hash(FieldNameBase, "members"): nil, Hash(FieldNameBase, "sub_groups"): nil,
}, },
}, },
}) })
type SubGroup struct {
Members []NodeID
Permissions Tree
}
type MemberOfPolicy struct {
PolicyHeader
Groups map[NodeID]map[string]Tree
}
func NewMemberOfPolicy(groups map[NodeID]map[string]Tree) MemberOfPolicy {
return MemberOfPolicy{
PolicyHeader: NewPolicyHeader(),
Groups: groups,
}
}
func (policy MemberOfPolicy) ContinueAllows(ctx *Context, current PendingACL, signal Signal) RuleResult {
sig, ok := signal.(*ReadResultSignal)
if ok == false {
return Deny
}
ctx.Log.Logf("group", "member_of_read_result: %+v", sig.Extensions)
group_ext_data, ok := sig.Extensions[GroupExtType]
if ok == false {
return Deny
}
sub_groups_ser, ok := group_ext_data["sub_groups"]
if ok == false {
return Deny
}
_, sub_groups_if, _, err := DeserializeValue(ctx, sub_groups_ser)
if err != nil {
return Deny
}
ext_sub_groups, ok := sub_groups_if.Interface().(map[string][]NodeID)
if ok == false {
return Deny
}
group, exists := policy.Groups[sig.NodeID]
if exists == false {
return Deny
}
for sub_group_name, permissions := range(group) {
ext_sub_group, exists := ext_sub_groups[sub_group_name]
if exists == true {
for _, member_id := range(ext_sub_group) {
if member_id == current.Principal {
if permissions.Allows(current.Action) == Allow {
return Allow
}
}
}
}
}
return Deny
}
// Send a read signal to Group to check if principal_id is a member of it
func (policy MemberOfPolicy) Allows(ctx *Context, principal_id NodeID, action Tree, node *Node) (Messages, RuleResult) {
var messages Messages = nil
for group_id, sub_groups := range(policy.Groups) {
if group_id == node.ID {
ext, err := GetExt[*GroupExt](node, GroupExtType)
if err != nil {
ctx.Log.Logf("group", "MemberOfPolicy with self ID error: %s", err)
} else {
for sub_group_name, sub_group := range(sub_groups) {
ext_sub_group, exists := ext.SubGroups[sub_group_name]
if exists == true {
for _, member := range(ext_sub_group) {
if member == principal_id {
if sub_group.Allows(action) == Allow {
return nil, Allow
}
break
}
}
}
}
}
} else {
// Send the read request to the group so that ContinueAllows can parse the response to check membership
messages = messages.Add(ctx, group_id, node, nil, NewReadSignal(map[ExtType][]string{
GroupExtType: {"sub_groups"},
}))
}
}
if len(messages) > 0 {
return messages, Pending
} else {
return nil, Deny
}
}
type GroupExt struct { type GroupExt struct {
Members []NodeID `gv:"members"` SubGroups map[string][]NodeID `gv:"sub_groups"`
} }
func NewGroupExt(members []NodeID) *GroupExt { func NewGroupExt(sub_groups map[string][]NodeID) *GroupExt {
if sub_groups == nil {
sub_groups = map[string][]NodeID{}
}
return &GroupExt{ return &GroupExt{
Members: members, SubGroups: sub_groups,
} }
} }
@ -64,24 +222,60 @@ func (ext *GroupExt) Process(ctx *Context, node *Node, source NodeID, signal Sig
switch sig := signal.(type) { switch sig := signal.(type) {
case *AddMemberSignal: case *AddMemberSignal:
if slices.Contains(ext.Members, sig.MemberID) == true { sub_group, exists := ext.SubGroups[sig.SubGroup]
if exists == false {
messages = messages.Add(ctx, source, node, nil, NewErrorSignal(sig.Id, "not_subgroup"))
} else {
if slices.Contains(sub_group, sig.MemberID) == true {
messages = messages.Add(ctx, source, node, nil, NewErrorSignal(sig.Id, "already_member")) messages = messages.Add(ctx, source, node, nil, NewErrorSignal(sig.Id, "already_member"))
} else { } else {
ext.Members = append(ext.Members, sig.MemberID) sub_group = append(sub_group, sig.MemberID)
ext.SubGroups[sig.SubGroup] = sub_group
messages = messages.Add(ctx, source, node, nil, NewSuccessSignal(sig.Id)) messages = messages.Add(ctx, source, node, nil, NewSuccessSignal(sig.Id))
changes = changes.Add("member_added") changes = changes.Add("member_added")
} }
}
case *RemoveMemberSignal: case *RemoveMemberSignal:
idx := slices.Index(ext.Members, sig.MemberID) sub_group, exists := ext.SubGroups[sig.SubGroup]
if exists == false {
messages = messages.Add(ctx, source, node, nil, NewErrorSignal(sig.Id, "not_subgroup"))
} else {
idx := slices.Index(sub_group, sig.MemberID)
if idx == -1 { if idx == -1 {
messages = messages.Add(ctx, source, node, nil, NewErrorSignal(sig.Id, "not_member")) messages = messages.Add(ctx, source, node, nil, NewErrorSignal(sig.Id, "not_member"))
} else { } else {
ext.Members = slices.Delete(ext.Members, idx, idx+1) sub_group = slices.Delete(sub_group, idx, idx+1)
ext.SubGroups[sig.SubGroup] = sub_group
messages = messages.Add(ctx, source, node, nil, NewSuccessSignal(sig.Id)) messages = messages.Add(ctx, source, node, nil, NewSuccessSignal(sig.Id))
changes = changes.Add("member_removed") changes = changes.Add("member_removed")
} }
} }
case *AddSubGroupSignal:
_, exists := ext.SubGroups[sig.Name]
if exists == true {
messages = messages.Add(ctx, source, node, nil, NewErrorSignal(sig.Id, "already_subgroup"))
} else {
ext.SubGroups[sig.Name] = []NodeID{}
changes = changes.Add("subgroup_added")
messages = messages.Add(ctx, source, node, nil, NewSuccessSignal(sig.Id))
}
case *RemoveSubGroupSignal:
_, exists := ext.SubGroups[sig.Name]
if exists == false {
messages = messages.Add(ctx, source, node, nil, NewErrorSignal(sig.Id, "not_subgroup"))
} else {
delete(ext.SubGroups, sig.Name)
changes = changes.Add("subgroup_removed")
messages = messages.Add(ctx, source, node, nil, NewSuccessSignal(sig.Id))
}
}
return messages, changes return messages, changes
} }

@ -12,18 +12,36 @@ func TestGroupAdd(t *testing.T) {
group, err := NewNode(ctx, nil, GroupNodeType, 10, nil, group_listener, NewGroupExt(nil)) group, err := NewNode(ctx, nil, GroupNodeType, 10, nil, group_listener, NewGroupExt(nil))
fatalErr(t, err) fatalErr(t, err)
add_subgroup_signal := NewAddSubGroupSignal("test_group")
messages := Messages{}
messages = messages.Add(ctx, group.ID, group, nil, add_subgroup_signal)
fatalErr(t, ctx.Send(messages))
resp_1, err := WaitForResponse(group_listener.Chan, 10*time.Millisecond, add_subgroup_signal.Id)
fatalErr(t, err)
error_1, is_error := resp_1.(*ErrorSignal)
if is_error {
t.Fatalf("Error returned: %s", error_1.Error)
}
user_id := RandID() user_id := RandID()
add_member_signal := NewAddMemberSignal(user_id) add_member_signal := NewAddMemberSignal("test_group", user_id)
messages := Messages{} messages = Messages{}
messages = messages.Add(ctx, group.ID, group, nil, add_member_signal) messages = messages.Add(ctx, group.ID, group, nil, add_member_signal)
fatalErr(t, ctx.Send(messages)) fatalErr(t, ctx.Send(messages))
_, err = WaitForResponse(group_listener.Chan, 10*time.Millisecond, add_member_signal.Id) resp_2, err := WaitForResponse(group_listener.Chan, 10*time.Millisecond, add_member_signal.Id)
fatalErr(t, err) fatalErr(t, err)
error_2, is_error := resp_2.(*ErrorSignal)
if is_error {
t.Fatalf("Error returned: %s", error_2.Error)
}
read_signal := NewReadSignal(map[ExtType][]string{ read_signal := NewReadSignal(map[ExtType][]string{
GroupExtType: {"members"}, GroupExtType: {"sub_groups"},
}) })
messages = Messages{} messages = Messages{}
@ -35,26 +53,35 @@ func TestGroupAdd(t *testing.T) {
read_response := response.(*ReadResultSignal) read_response := response.(*ReadResultSignal)
members_serialized := read_response.Extensions[GroupExtType]["members"] sub_groups_serialized := read_response.Extensions[GroupExtType]["sub_groups"]
_, member_value, remaining, err := DeserializeValue(ctx, members_serialized) _, sub_groups_value, remaining, err := DeserializeValue(ctx, sub_groups_serialized)
if len(remaining.Data) > 0 { if len(remaining.Data) > 0 {
t.Fatalf("Data remaining after deserializing member list: %d", len(remaining.Data)) t.Fatalf("Data remaining after deserializing subgroups: %d", len(remaining.Data))
} }
member_list, ok := member_value.Interface().([]NodeID) sub_groups, ok := sub_groups_value.Interface().(map[string][]NodeID)
if ok != true { if ok != true {
t.Fatalf("member_list wrong type %s", member_value.Type()) t.Fatalf("sub_groups wrong type %s", sub_groups_value.Type())
}
if len(sub_groups) != 1 {
t.Fatalf("sub_groups wrong length %d", len(sub_groups))
}
test_subgroup, exists := sub_groups["test_group"]
if exists == false {
t.Fatal("test_group not in subgroups")
} }
if len(member_list) != 1 { if len(test_subgroup) != 1 {
t.Fatalf("member_list wrong length %d", len(member_list)) t.Fatalf("test_group wrong size %d/1", len(test_subgroup))
} }
if member_list[0] != user_id { if test_subgroup[0] != user_id {
t.Fatalf("member_list wrong value %s", member_list[0]) t.Fatalf("sub_groups wrong value %s", test_subgroup[0])
} }
ctx.Log.Logf("test", "Read Response: %+v", read_response) ctx.Log.Logf("test", "Read Response: %+v", read_response)

@ -327,3 +327,47 @@ func (ext *LockableExt) Process(ctx *Context, node *Node, source NodeID, signal
return messages, changes return messages, changes
} }
type RequirementOfPolicy struct {
PerNodePolicy
}
func NewRequirementOfPolicy(dep_rules map[NodeID]Tree) RequirementOfPolicy {
return RequirementOfPolicy {
PerNodePolicy: NewPerNodePolicy(dep_rules),
}
}
func (policy RequirementOfPolicy) ContinueAllows(ctx *Context, current PendingACL, signal Signal) RuleResult {
sig, ok := signal.(*ReadResultSignal)
if ok == false {
return Deny
}
ext, ok := sig.Extensions[LockableExtType]
if ok == false {
return Deny
}
reqs_ser, ok := ext["requirements"]
if ok == false {
return Deny
}
_, reqs_if, _, err := DeserializeValue(ctx, reqs_ser)
if err != nil {
return Deny
}
requirements, ok := reqs_if.Interface().(map[NodeID]ReqState)
if ok == false {
return Deny
}
for req, _ := range(requirements) {
if req == current.Principal {
return policy.NodeRules[sig.NodeID].Allows(current.Action)
}
}
return Deny
}

@ -40,121 +40,6 @@ func (policy PerNodePolicy) ContinueAllows(ctx *Context, current PendingACL, sig
return Deny return Deny
} }
type RequirementOfPolicy struct {
PerNodePolicy
}
func NewRequirementOfPolicy(dep_rules map[NodeID]Tree) RequirementOfPolicy {
return RequirementOfPolicy {
PerNodePolicy: NewPerNodePolicy(dep_rules),
}
}
func (policy RequirementOfPolicy) ContinueAllows(ctx *Context, current PendingACL, signal Signal) RuleResult {
sig, ok := signal.(*ReadResultSignal)
if ok == false {
return Deny
}
ext, ok := sig.Extensions[LockableExtType]
if ok == false {
return Deny
}
reqs_ser, ok := ext["requirements"]
if ok == false {
return Deny
}
_, reqs_if, _, err := DeserializeValue(ctx, reqs_ser)
if err != nil {
return Deny
}
requirements, ok := reqs_if.Interface().(map[NodeID]ReqState)
if ok == false {
return Deny
}
for req, _ := range(requirements) {
if req == current.Principal {
return policy.NodeRules[sig.NodeID].Allows(current.Action)
}
}
return Deny
}
type MemberOfPolicy struct {
PerNodePolicy
}
func NewMemberOfPolicy(group_rules map[NodeID]Tree) MemberOfPolicy {
return MemberOfPolicy{
PerNodePolicy: NewPerNodePolicy(group_rules),
}
}
func (policy MemberOfPolicy) ContinueAllows(ctx *Context, current PendingACL, signal Signal) RuleResult {
sig, ok := signal.(*ReadResultSignal)
if ok == false {
return Deny
}
ctx.Log.Logf("group", "member_of_read_result: %+v", sig.Extensions)
group_ext_data, ok := sig.Extensions[GroupExtType]
if ok == false {
return Deny
}
members_ser, ok := group_ext_data["members"]
if ok == false {
return Deny
}
_, members_if, _, err := DeserializeValue(ctx, members_ser)
if err != nil {
return Deny
}
members, ok := members_if.Interface().([]NodeID)
if ok == false {
return Deny
}
for _, member := range(members) {
if member == current.Principal {
return policy.NodeRules[sig.NodeID].Allows(current.Action)
}
}
return Deny
}
// Send a read signal to Group to check if principal_id is a member of it
func (policy MemberOfPolicy) Allows(ctx *Context, principal_id NodeID, action Tree, node *Node) (Messages, RuleResult) {
msgs := Messages{}
for id, rule := range(policy.NodeRules) {
if id == node.ID {
ext, err := GetExt[*GroupExt](node, GroupExtType)
if err == nil {
for _, member := range(ext.Members) {
if member == principal_id {
if rule.Allows(action) == Allow {
return nil, Allow
}
}
}
}
} else {
msgs = msgs.Add(ctx, id, node, nil, NewReadSignal(map[ExtType][]string{
GroupExtType: []string{"members"},
}))
}
}
return msgs, Pending
}
func CopyTree(tree Tree) Tree { func CopyTree(tree Tree) Tree {
if tree == nil { if tree == nil {
return nil return nil

@ -104,6 +104,8 @@ var (
RemoveMemberSignalType = NewSignalType("REMOVE_MEMBER") RemoveMemberSignalType = NewSignalType("REMOVE_MEMBER")
AddMemberSignalType = NewSignalType("ADD_MEMBER") AddMemberSignalType = NewSignalType("ADD_MEMBER")
ACLSignalType = NewSignalType("ACL") ACLSignalType = NewSignalType("ACL")
AddSubGroupSignalType = NewSignalType("ADD_SUBGROUP")
RemoveSubGroupSignalType = NewSignalType("REMOVE_SUBGROUP")
MemberOfPolicyType = NewPolicyType("USER_OF") MemberOfPolicyType = NewPolicyType("USER_OF")
RequirementOfPolicyType = NewPolicyType("REQUIEMENT_OF") RequirementOfPolicyType = NewPolicyType("REQUIEMENT_OF")

@ -6,6 +6,11 @@ import (
"fmt" "fmt"
) )
func TestSerializeTest(t *testing.T) {
ctx := logTestContext(t, []string{"test", "serialize"})
testSerialize(t, ctx, map[string][]NodeID{"test_group": {RandID(), RandID(), RandID()}})
}
func TestSerializeBasic(t *testing.T) { func TestSerializeBasic(t *testing.T) {
ctx := logTestContext(t, []string{"test"}) ctx := logTestContext(t, []string{"test"})
testSerializeComparable[string](t, ctx, "test") testSerializeComparable[string](t, ctx, "test")
@ -29,6 +34,7 @@ func TestSerializeBasic(t *testing.T) {
testSerialize(t, ctx, map[int8]map[*int8]string{}) testSerialize(t, ctx, map[int8]map[*int8]string{})
var i interface{} = nil var i interface{} = nil
testSerialize(t, ctx, i) testSerialize(t, ctx, i)