Added policy.go

graph-rework-2
noah metz 2023-07-20 23:19:10 -06:00
parent a88c704c57
commit 61fd7d61aa
5 changed files with 259 additions and 6 deletions

@ -188,6 +188,14 @@ func NewContext(db * badger.DB, log Logger) * Context {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = ctx.RegisterNodeType(NewNodeDef((*PerNodePolicy)(nil), LoadPerNodePolicy, GQLTypeGraphNode()))
if err != nil {
panic(err)
}
err = ctx.RegisterNodeType(NewNodeDef((*AllNodePolicy)(nil), LoadAllNodePolicy, GQLTypeGraphNode()))
if err != nil {
panic(err)
}
ctx.AddGQLType(GQLTypeSignal()) ctx.AddGQLType(GQLTypeSignal())

@ -70,15 +70,22 @@ func TestGQLDBLoad(t * testing.T) {
u1_r := NewUser("Test User", time.Now(), &u1_key.PublicKey, u1_shared) u1_r := NewUser("Test User", time.Now(), &u1_key.PublicKey, u1_shared)
u1 := &u1_r u1 := &u1_r
p1_r := NewAllNodePolicy(RandID(), []string{"*"})
p1 := &p1_r
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
fatalErr(t, err) fatalErr(t, err)
gql_r := NewGQLThread(RandID(), "GQL Thread", "init", ":0", ecdh.P256(), key) gql_r := NewGQLThread(RandID(), "GQL Thread", "init", ":0", ecdh.P256(), key)
gql := &gql_r gql := &gql_r
info := NewParentThreadInfo(true, "start", "restore") info := NewParentThreadInfo(true, "start", "restore")
err = UpdateStates(ctx, []Node{gql, t1, l1, u1}, func(nodes NodeMap) error { err = UpdateStates(ctx, []Node{gql, t1, l1, u1, p1}, func(nodes NodeMap) error {
err := u1.AddPolicy(p1)
if err != nil {
return err
}
gql.Users[KeyID(&u1_key.PublicKey)] = u1 gql.Users[KeyID(&u1_key.PublicKey)] = u1
err := LinkLockables(ctx, gql, []Lockable{l1}, nodes) err = LinkLockables(ctx, gql, []Lockable{l1}, nodes)
if err != nil { if err != nil {
return err return err
} }

@ -60,6 +60,7 @@ func (state * SimpleLockable) Type() NodeType {
} }
type SimpleLockableJSON struct { type SimpleLockableJSON struct {
GraphNodeJSON
Name string `json:"name"` Name string `json:"name"`
Owner *NodeID `json:"owner"` Owner *NodeID `json:"owner"`
Dependencies []NodeID `json:"dependencies"` Dependencies []NodeID `json:"dependencies"`
@ -98,7 +99,11 @@ func NewSimpleLockableJSON(lockable *SimpleLockable) SimpleLockableJSON {
locks_held[lockable_id.String()] = &str locks_held[lockable_id.String()] = &str
} }
} }
node_json := NewGraphNodeJSON(&lockable.GraphNode)
return SimpleLockableJSON{ return SimpleLockableJSON{
GraphNodeJSON: node_json,
Name: lockable.name, Name: lockable.name,
Owner: owner_id, Owner: owner_id,
Dependencies: dependency_ids, Dependencies: dependency_ids,
@ -560,7 +565,7 @@ func RestoreSimpleLockable(ctx * Context, lockable Lockable, j SimpleLockableJSO
} }
o_l, ok := o.(Lockable) o_l, ok := o.(Lockable)
if ok == false { if ok == false {
return fmt.Errorf("%s is not a Lockable", o.ID()) return fmt.Errorf("%s is not a Lockable", *j.Owner)
} }
lockable.SetOwner(o_l) lockable.SetOwner(o_l)
} }
@ -615,5 +620,5 @@ func RestoreSimpleLockable(ctx * Context, lockable Lockable, j SimpleLockableJSO
lockable.RecordLock(l_l, h_l) lockable.RecordLock(l_l, h_l)
} }
return nil return RestoreGraphNode(ctx, lockable, j.GraphNodeJSON, nodes)
} }

@ -6,6 +6,7 @@ import (
badger "github.com/dgraph-io/badger/v3" badger "github.com/dgraph-io/badger/v3"
"fmt" "fmt"
"encoding/binary" "encoding/binary"
"encoding/json"
"crypto/sha512" "crypto/sha512"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
@ -56,13 +57,22 @@ func RandID() NodeID {
// A Node represents data that can be read by multiple goroutines and written to by one, with a unique ID attached, and a method to process updates(including propagating them to connected nodes) // A Node represents data that can be read by multiple goroutines and written to by one, with a unique ID attached, and a method to process updates(including propagating them to connected nodes)
// RegisterChannel and UnregisterChannel are used to connect arbitrary listeners to the node // RegisterChannel and UnregisterChannel are used to connect arbitrary listeners to the node
type Node interface { type Node interface {
// State Locking interface
sync.Locker sync.Locker
RLock() RLock()
RUnlock() RUnlock()
// Serialize the Node for the database // Serialize the Node for the database
Serialize() ([]byte, error) Serialize() ([]byte, error)
// Nodes have an ID, type, and ACL policies
ID() NodeID ID() NodeID
Type() NodeType Type() NodeType
Allowed(action string, principal NodeID) bool
AddPolicy(Policy) error
RemovePolicy(Policy) error
// Send a GraphSignal to the node, requires that the node is locked for read so that it can propagate // Send a GraphSignal to the node, requires that the node is locked for read so that it can propagate
Signal(ctx *Context, signal GraphSignal, nodes NodeMap) error Signal(ctx *Context, signal GraphSignal, nodes NodeMap) error
// Register a channel to receive updates sent to the node // Register a channel to receive updates sent to the node
@ -78,11 +88,81 @@ type GraphNode struct {
id NodeID id NodeID
listeners map[NodeID]chan GraphSignal listeners map[NodeID]chan GraphSignal
policies map[NodeID]Policy
}
type GraphNodeJSON struct {
Policies []NodeID `json:"policies"`
} }
// GraphNode doesn't serialize any additional information by default
func (node * GraphNode) Serialize() ([]byte, error) { func (node * GraphNode) Serialize() ([]byte, error) {
return nil, nil node_json := NewGraphNodeJSON(node)
return json.MarshalIndent(&node_json, "", " ")
}
func (node *GraphNode) Allowed(action string, principal NodeID) bool {
for _, policy := range(node.policies) {
if policy.Allows(action, principal) == true {
return true
}
}
return false
}
func (node *GraphNode) AddPolicy(policy Policy) error {
if policy == nil {
return fmt.Errorf("Cannot add nil as a policy")
}
_, exists := node.policies[policy.ID()]
if exists == true {
return fmt.Errorf("%s is already a policy for %s", policy.ID().String(), node.ID().String())
}
node.policies[policy.ID()] = policy
return nil
}
func (node *GraphNode) RemovePolicy(policy Policy) error {
if policy == nil {
return fmt.Errorf("Cannot add nil as a policy")
}
_, exists := node.policies[policy.ID()]
if exists == false {
return fmt.Errorf("%s is not a policy for %s", policy.ID().String(), node.ID().String())
}
delete(node.policies, policy.ID())
return nil
}
func NewGraphNodeJSON(node *GraphNode) GraphNodeJSON {
policies := make([]NodeID, len(node.policies))
i := 0
for _, policy := range(node.policies) {
policies[i] = policy.ID()
i += 1
}
return GraphNodeJSON{
Policies: policies,
}
}
func RestoreGraphNode(ctx *Context, node Node, j GraphNodeJSON, nodes NodeMap) error {
for _, policy_id := range(j.Policies) {
policy_ptr, err := LoadNodeRecurse(ctx, policy_id, nodes)
if err != nil {
return err
}
policy, ok := policy_ptr.(Policy)
if ok == false {
return fmt.Errorf("%s is not a Policy", policy_id)
}
node.AddPolicy(policy)
}
return nil
} }
func LoadGraphNode(ctx * Context, id NodeID, data []byte, nodes NodeMap)(Node, error) { func LoadGraphNode(ctx * Context, id NodeID, data []byte, nodes NodeMap)(Node, error) {
@ -153,6 +233,7 @@ func NewGraphNode(id NodeID) GraphNode {
return GraphNode{ return GraphNode{
id: id, id: id,
listeners: map[NodeID]chan GraphSignal{}, listeners: map[NodeID]chan GraphSignal{},
policies: map[NodeID]Policy{},
} }
} }

@ -0,0 +1,152 @@
package graphvent
import (
"encoding/json"
)
// A policy represents a set of rules attached to a Node that allow it to preform actions
type Policy interface {
Node
// Returns true if the policy allows the action on the given principal
Allows(action string, principal NodeID) bool
}
type PerNodePolicy struct {
GraphNode
AllowedActions map[NodeID][]string
}
type PerNodePolicyJSON struct {
GraphNodeJSON
AllowedActions map[string][]string `json:"allowed_actions"`
}
func (policy *PerNodePolicy) Type() NodeType {
return NodeType("per_node_policy")
}
func (policy *PerNodePolicy) Serialize() ([]byte, error) {
allowed_actions := map[string][]string{}
for principal, actions := range(policy.AllowedActions) {
allowed_actions[principal.String()] = actions
}
return json.MarshalIndent(&PerNodePolicyJSON{
GraphNodeJSON: NewGraphNodeJSON(&policy.GraphNode),
AllowedActions: allowed_actions,
}, "", " ")
}
func NewPerNodePolicy(id NodeID, allowed_actions map[NodeID][]string) PerNodePolicy {
if allowed_actions == nil {
allowed_actions = map[NodeID][]string{}
}
return PerNodePolicy{
GraphNode: NewGraphNode(id),
AllowedActions: allowed_actions,
}
}
func LoadPerNodePolicy(ctx *Context, id NodeID, data []byte, nodes NodeMap) (Node, error) {
var j PerNodePolicyJSON
err := json.Unmarshal(data, &j)
if err != nil {
return nil, err
}
allowed_actions := map[NodeID][]string{}
for principal_str, actions := range(j.AllowedActions) {
principal_id, err := ParseID(principal_str)
if err != nil {
return nil, err
}
allowed_actions[principal_id] = actions
}
policy := NewPerNodePolicy(id, allowed_actions)
nodes[id] = &policy
err = RestoreGraphNode(ctx, &policy.GraphNode, j.GraphNodeJSON, nodes)
if err != nil {
return nil, err
}
return &policy, nil
}
func (policy *PerNodePolicy) Allows(action string, principal NodeID) bool {
actions, exists := policy.AllowedActions[principal]
if exists == false {
return false
}
for _, a := range(actions) {
if a == action {
return true
}
}
return false
}
type AllNodePolicy struct {
GraphNode
AllowedActions []string
}
type AllNodePolicyJSON struct {
GraphNodeJSON
AllowedActions []string `json:"allowed_actions"`
}
func (policy *AllNodePolicy) Type() NodeType {
return NodeType("all_node_policy")
}
func (policy *AllNodePolicy) Serialize() ([]byte, error) {
return json.MarshalIndent(&AllNodePolicyJSON{
GraphNodeJSON: NewGraphNodeJSON(&policy.GraphNode),
AllowedActions: policy.AllowedActions,
}, "", " ")
}
func NewAllNodePolicy(id NodeID, allowed_actions []string) AllNodePolicy {
if allowed_actions == nil {
allowed_actions = []string{}
}
return AllNodePolicy{
GraphNode: NewGraphNode(id),
AllowedActions: allowed_actions,
}
}
func LoadAllNodePolicy(ctx *Context, id NodeID, data []byte, nodes NodeMap) (Node, error) {
var j AllNodePolicyJSON
err := json.Unmarshal(data, &j)
if err != nil {
return nil, err
}
policy := NewAllNodePolicy(id, j.AllowedActions)
nodes[id] = &policy
err = RestoreGraphNode(ctx, &policy.GraphNode, j.GraphNodeJSON, nodes)
if err != nil {
return nil, err
}
return &policy, nil
}
func (policy *AllNodePolicy) Allows(action string, principal NodeID) bool {
for _, a := range(policy.AllowedActions) {
if a == action {
return true
}
}
return false
}