1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
use std::collections::{HashSet, VecDeque};
type NodeIdx = usize;
type EdgeIdx = usize;
struct RelationGraph {
nodes: Vec<Node>,
edges: Vec<Edge>,
}
#[derive(Hash, PartialEq, Eq)]
struct Node {
id: u64,
is_namespace: bool,
edge_list_head: Option<EdgeIdx>,
}
struct Edge {
dst: NodeIdx,
op: Option<Operator>,
next_edge: Option<EdgeIdx>,
}
enum Operator {
And,
Or,
Difference,
Complement,
AndAll,
OrAll,
DifferenceAll,
ComplementAll,
}
impl RelationGraph {
pub fn new() -> Self {
// As I think about how to serialize this graph, when reading from the DB
// I should chunk up the namespaces so that I can read-on-demand when I
// encounter a namespace that has not yet been loaded, and in this way
// I don't need to deal with the entire graph...
//
// but that's a totally unnecessary fun optimization.
RelationGraph {
nodes: vec![],
edges: vec![],
}
}
pub fn add_node(&mut self, id: u64, is_namespace: bool) -> NodeIdx {
let idx = self.nodes.len();
self.nodes.push(Node {
id,
is_namespace,
edge_list_head: None,
});
idx
}
pub fn add_edge(
&mut self,
src: NodeIdx,
dst: NodeIdx,
op: Option<Operator>,
follow: Option<NodeIdx>,
) {
let edge_idx = self.edges.len();
let node = &mut self.nodes[src];
// TODO: dupe check
self.edges.push(Edge {
dst,
op,
next_edge: node.edge_list_head,
});
node.edge_list_head = Some(edge_idx);
}
// doc:2#viewer@user:1
pub fn find_leaves(&self, src: NodeIdx, filterIdx: Option<NodeIdx>) -> Vec<NodeIdx> {
let mut all_leaves = vec![];
// let mut and_leaves = vec![];
// let mut exc_leaves = vec![];
// the output should basically be leaves to look up
// for example
// the leaves of doc might be (user), <(organization, admin)>, <(role, foo)>
// and since (organization, admin) has leaves (user) as well
// if the user is in that final set, then they are valid
// and those leaves would be queried as relation tuples saved in the DB
println!("HERE I AM");
let start = self.nodes.get(src);
let mut seen = HashSet::new();
let mut q = VecDeque::new();
seen.insert(start);
q.push_back(start);
// I need to build a chain of ops
// [AND, OR, OR, AND, OR, ...]
// at each stage, I need to apply the operator chain
while let Some(&Some(node)) = q.iter().next() {
if node.is_namespace {
all_leaves.push(node);
}
// for edge in edge list
println!("IN QUEUE\n");
println!("node: {:?}\n", node.id);
q.pop_front();
}
println!("DONE");
// task...starting at doc_viewer, return all leaf nodes
// to do this....
// check all children
// if child is OR
// if child is leaf, return child
// else return all of these children
// if child is AND
// if child is leaf, return if also present in all other nodes
// else return all children which are also present in other nodes
// if child is Difference
// if child is leaf, do not include and remove if it part of the user set
// else remove all of these children
// TOTAL pool of leafs
// Intersection leaves (i.e. ONLY leaves which MUST exist)
// exclusion leaves (i.e. leaves which CANNOT exist)
// bfs build query...
// execute query
// start at nodeIdx: doc_viewer
// expand until I find all leaf nodes.
// we are interested in leaf nodes with nodeIdx: user
// return all leaf nodeIdx that match the filter. If no filter provided, return all leaf idx.
// TODO: optionally build the expansion path by pushing every intermediate step as the bfs is walked.
// with each step of the bfs, perform the operator.
// e.g. if first step is doc_viewer -> user with operator OR then we have leaf user with operator chain [OR]
// the next step might be doc_viewer -> doc_editor with step OR and doc_editor -> user with step OR, so we have leaf user with chain [OR, OR]
// the next step might be doc_viewer -> doc_auditor with OR
// then doc_auditor -> doc_editor with OR
// then doc_editor -> doc_user with OR
// then doc_editor -> doc_owner
// then doc_auditor -> doc_owner with Difference
//
todo!()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn build_graph_test() {
let mut g = RelationGraph::new();
let user = g.add_node(1, true);
let role = g.add_node(2, true);
let group = g.add_node(3, true);
let doc = g.add_node(4, true);
let role_member = g.add_node(5, false);
let group_member = g.add_node(6, false);
let group_admin = g.add_node(7, false);
let doc_owner = g.add_node(8, false);
let doc_editor = g.add_node(9, false);
let doc_viewer = g.add_node(10, false);
let doc_auditor = g.add_node(11, false);
let doc_parent = g.add_node(12, false);
g.add_edge(role, role_member, None, None);
g.add_edge(role_member, user, Some(Operator::Or), None);
g.add_edge(role_member, group_member, Some(Operator::Or), None);
g.add_edge(group, group_member, None, None);
g.add_edge(group, group_admin, None, None);
g.add_edge(group_member, user, Some(Operator::Or), None);
g.add_edge(group_member, group_admin, Some(Operator::Or), None);
g.add_edge(group_admin, user, Some(Operator::Or), None);
g.add_edge(doc, doc_owner, None, None);
g.add_edge(doc, doc_editor, None, None);
g.add_edge(doc, doc_viewer, None, None);
g.add_edge(doc, doc_auditor, None, None);
g.add_edge(doc, doc_parent, None, None);
g.add_edge(doc_owner, user, Some(Operator::Or), None);
g.add_edge(doc_editor, user, Some(Operator::Or), None);
g.add_edge(doc_editor, doc_owner, Some(Operator::Or), None);
g.add_edge(doc_viewer, user, Some(Operator::Or), None);
g.add_edge(doc_viewer, doc_editor, Some(Operator::Or), None);
g.add_edge(doc_viewer, doc_parent, Some(Operator::Or), Some(doc_viewer));
g.add_edge(doc_viewer, user, Some(Operator::OrAll), None);
g.add_edge(doc_viewer, doc_auditor, Some(Operator::Or), None);
g.add_edge(doc_auditor, doc_editor, Some(Operator::Difference), None);
g.find_leaves(doc_viewer, None);
assert_eq!(1, 2);
}
}
|