[31b67d5] | 1 | package net.deterlab.abac; |
---|
| 2 | |
---|
| 3 | import org.apache.commons.collections15.*; |
---|
| 4 | |
---|
| 5 | import edu.uci.ics.jung.graph.*; |
---|
| 6 | import edu.uci.ics.jung.graph.util.*; |
---|
| 7 | |
---|
| 8 | import java.util.*; |
---|
| 9 | |
---|
| 10 | /** |
---|
| 11 | * A class for making queries against the graph. It supports direct queries as |
---|
| 12 | * well as reachability in either direction. See the run method for details. |
---|
[b67a7ac] | 13 | * @author <a href="http://abac.deterlab.net">ISI ABAC team</a> |
---|
[4560b65] | 14 | * @version 1.4 |
---|
[31b67d5] | 15 | */ |
---|
[0595372] | 16 | class Query { |
---|
[b67a7ac] | 17 | /** Internal graph representation */ |
---|
[31b67d5] | 18 | private Graph<Role,Credential> g; |
---|
[2d45fd4] | 19 | /** Count of vertices in the most recent calculate path */ |
---|
| 20 | private boolean valid; |
---|
[31b67d5] | 21 | |
---|
[b67a7ac] | 22 | /** |
---|
| 23 | * Create a query to be run against the credential graph. |
---|
| 24 | * @param g a Graph represneting the credentials and implicit connections. |
---|
| 25 | */ |
---|
[31b67d5] | 26 | public Query(Graph<Role,Credential> g) { |
---|
| 27 | this.g = g; |
---|
[2d45fd4] | 28 | valid = false; |
---|
[31b67d5] | 29 | } |
---|
| 30 | |
---|
| 31 | /** |
---|
| 32 | * Run a query against the graph, returning a graph of the results. If the |
---|
| 33 | * results are empty or the query fails, an empty graph is returned. When |
---|
| 34 | * derived edges are involved, the subgraphs that imply those edges are |
---|
| 35 | * included. |
---|
[b67a7ac] | 36 | * @param attr a String containing the role to look for |
---|
| 37 | * @param prin a String containing the principal |
---|
| 38 | * @return a Graph with the proof or partial proof. |
---|
[31b67d5] | 39 | */ |
---|
| 40 | public Graph<Role,Credential> run(String attr, String prin) { |
---|
[2d45fd4] | 41 | Role attribute = (!attr.isEmpty()) ? new Role(attr) : null; |
---|
| 42 | Role principal = (!prin.isEmpty()) ? new Role(prin) : null; |
---|
[31b67d5] | 43 | Graph<Role,Credential> ret = |
---|
| 44 | Graphs.<Role,Credential>synchronizedDirectedGraph( |
---|
| 45 | new DirectedSparseGraph<Role,Credential>()); |
---|
[2d45fd4] | 46 | ArrayList<Graph<Role, Credential> > subs = |
---|
| 47 | new ArrayList<Graph<Role, Credential> >(); |
---|
| 48 | Set<Role> hasAttr = null; |
---|
| 49 | |
---|
| 50 | // System.out.println("In run " + attr + " " + prin); |
---|
| 51 | |
---|
| 52 | if (attribute == null && principal == null ) { |
---|
| 53 | valid = false; |
---|
| 54 | return ret; |
---|
| 55 | } |
---|
| 56 | else if (principal != null ) { |
---|
| 57 | /* This branch happens if attribute is null or not, and either is |
---|
| 58 | * fine for the bfs routine. */ |
---|
| 59 | valid = bfs(principal, attribute, ret, |
---|
| 60 | new forwardSearch<Role, Credential>()); |
---|
| 61 | } else { |
---|
| 62 | /* attribute is != null here */ |
---|
| 63 | valid = bfs(attribute, principal, ret, |
---|
| 64 | new reverseSearch<Role, Credential>()); |
---|
| 65 | } |
---|
| 66 | |
---|
| 67 | if ( principal == null ) hasAttr = find_principals(attribute); |
---|
| 68 | else { |
---|
| 69 | hasAttr =new HashSet<Role>(); |
---|
| 70 | hasAttr.add(principal); |
---|
| 71 | } |
---|
| 72 | |
---|
| 73 | /* Now ret contains the primary path of the proof, for each linking |
---|
| 74 | * role or intersection on that path, we construct a Query object to |
---|
| 75 | * make that subproof. */ |
---|
| 76 | for (Credential c: ret.getEdges() ) { |
---|
| 77 | Role head = c.head(); |
---|
| 78 | Role tail = c.tail(); |
---|
| 79 | |
---|
| 80 | if ( head.is_linking()) { |
---|
| 81 | Query subq = new Query(g); |
---|
| 82 | subs.add(subq.run(head.linked_role(), tail.principal())); |
---|
| 83 | if ( !subq.successful()) |
---|
| 84 | throw new RuntimeException("Cannot prove sub-proof: " + |
---|
| 85 | head.linked_role() + " <- " + tail.principal() + |
---|
| 86 | " something is very wrong!"); |
---|
| 87 | } else if ( head.is_intersection() ) { |
---|
| 88 | try { |
---|
| 89 | for (Role r: head.prereqs()) { |
---|
| 90 | for (Role p : hasAttr) { |
---|
| 91 | Query subq = new Query(g); |
---|
| 92 | subs.add(subq.run(r.toString(), p.toString())); |
---|
| 93 | if ( !subq.successful()) |
---|
| 94 | throw new RuntimeException( |
---|
| 95 | "Cannot prove sub-proof: " + |
---|
| 96 | r.toString() + " <- " + p + |
---|
| 97 | " something is very wrong!"); |
---|
| 98 | } |
---|
| 99 | } |
---|
| 100 | } |
---|
| 101 | catch (ABACException ignored) { } |
---|
| 102 | } |
---|
| 103 | } |
---|
| 104 | /* Now subs has all the subsidiary proofs in it, merge 'em */ |
---|
| 105 | for (Graph<Role, Credential> sg: subs) { |
---|
| 106 | for (Role r: sg.getVertices()) |
---|
| 107 | if (!ret.containsVertex(r)) |
---|
| 108 | ret.addVertex(r); |
---|
| 109 | for (Credential c: sg.getEdges() ) |
---|
| 110 | if ( !ret.containsEdge(c)) |
---|
| 111 | ret.addEdge(c, c.head(), c.tail()); |
---|
| 112 | } |
---|
[31b67d5] | 113 | return ret; |
---|
| 114 | } |
---|
| 115 | |
---|
[2d45fd4] | 116 | |
---|
| 117 | |
---|
[31b67d5] | 118 | /** |
---|
| 119 | * Returns true after running a query that returns a non-empty set of |
---|
| 120 | * vertices. |
---|
[b67a7ac] | 121 | * @return a boolean, true after running a query that returns a non-empty |
---|
| 122 | * set of vertices. |
---|
[31b67d5] | 123 | */ |
---|
| 124 | public boolean successful() { |
---|
[2d45fd4] | 125 | return valid; |
---|
[31b67d5] | 126 | } |
---|
| 127 | |
---|
| 128 | /** |
---|
| 129 | * Returns a collection of principals reachable from a Role when |
---|
| 130 | * traversing edges in the reverse direction. |
---|
[b67a7ac] | 131 | * @param n the Role to start from |
---|
| 132 | * @return a Set containinf the principals |
---|
[31b67d5] | 133 | */ |
---|
[cac4c76] | 134 | public Set<Role> find_principals(Role n) { |
---|
| 135 | Set<Role> principals = new HashSet<Role>(); |
---|
[2d45fd4] | 136 | bfs(n, null, null, new principalSearch(principals)); |
---|
[31b67d5] | 137 | return principals; |
---|
| 138 | } |
---|
| 139 | |
---|
| 140 | /** |
---|
[2d45fd4] | 141 | * Class extended to specialize the behavior of the bfs routine at the core |
---|
| 142 | * of the query system. There are two ways to customize - the traversal |
---|
| 143 | * order which controls if edges are followed in the orientation of the |
---|
| 144 | * graph or in the reverse orientation and a member called when an edge is |
---|
| 145 | * added to an output graph. |
---|
[31b67d5] | 146 | */ |
---|
[2d45fd4] | 147 | private abstract class bfsSearchDirection<V,E> { |
---|
[b67a7ac] | 148 | /** |
---|
[2d45fd4] | 149 | * Return a collection of outgoing edges under the search direction's |
---|
| 150 | * interpretation. |
---|
| 151 | * @param g the Graph<V, E> being traversed |
---|
| 152 | * @param v a V, the vertex from which the edges come |
---|
| 153 | * @return a collection of outgoing edges under the search direction's |
---|
| 154 | * interpretation. |
---|
[b67a7ac] | 155 | */ |
---|
[2d45fd4] | 156 | public abstract Collection<E> forwardEdges(Graph<V, E> g, V v); |
---|
[b67a7ac] | 157 | /** |
---|
[2d45fd4] | 158 | * Return a collection of incoming edges under the search direction's |
---|
| 159 | * interpretation. |
---|
| 160 | * @param g the Graph<V, E> being traversed |
---|
| 161 | * @param v a V, the vertex into which the edges come |
---|
| 162 | * @return a collection of outgoing edges under the search direction's |
---|
| 163 | * interpretation. |
---|
[b67a7ac] | 164 | */ |
---|
[2d45fd4] | 165 | public abstract Collection<E> backwardEdges(Graph<V, E> g, V v); |
---|
[b67a7ac] | 166 | /** |
---|
[2d45fd4] | 167 | * Return the source of this edge under the search direction's |
---|
| 168 | * interpretation. |
---|
| 169 | * @param g the Graph<V, E> being traversed |
---|
| 170 | * @param e an E, the edge being considered |
---|
| 171 | * @return the source of this edge under the search direction's |
---|
| 172 | * interpretation. |
---|
| 173 | **/ |
---|
| 174 | public abstract V getSource(Graph<V, E> g, E e); |
---|
[b67a7ac] | 175 | /** |
---|
[2d45fd4] | 176 | * Return the destination of this edge under the search direction's |
---|
| 177 | * interpretation. |
---|
| 178 | * @param g the Graph<V, E> being traversed |
---|
| 179 | * @param e an E, the edge being considered |
---|
| 180 | * @return the destination of this edge under the search direction's |
---|
| 181 | * interpretation. |
---|
| 182 | **/ |
---|
| 183 | public abstract V getDest(Graph<V, E> g, E e); |
---|
[b67a7ac] | 184 | /** |
---|
[2d45fd4] | 185 | * Called when an edge is added; do nothing. |
---|
| 186 | * @param e an E edge being added. |
---|
[b67a7ac] | 187 | */ |
---|
[2d45fd4] | 188 | public void keptEdge(E e) { } |
---|
[31b67d5] | 189 | } |
---|
| 190 | |
---|
| 191 | /** |
---|
[2d45fd4] | 192 | * Class that runs the BFS along the graph as defined. |
---|
[31b67d5] | 193 | */ |
---|
[2d45fd4] | 194 | private class forwardSearch<V,E> extends bfsSearchDirection<V,E> { |
---|
[b67a7ac] | 195 | /** |
---|
[2d45fd4] | 196 | * Return a collection of outgoing edges; outgoing is defined by the |
---|
| 197 | * graph. |
---|
| 198 | * @param g the Graph<V, E> being traversed |
---|
| 199 | * @param v a V, the vertex from which the edges come |
---|
| 200 | * @return a collection of outgoing edges |
---|
[b67a7ac] | 201 | */ |
---|
[2d45fd4] | 202 | public Collection<E> forwardEdges(Graph<V, E> g, V v) { |
---|
| 203 | return g.getOutEdges(v); |
---|
| 204 | } |
---|
[b67a7ac] | 205 | /** |
---|
[2d45fd4] | 206 | * Return a collection of incoming edges; incoming is defined by the |
---|
| 207 | * graph. |
---|
| 208 | * @param g the Graph<V, E> being traversed |
---|
| 209 | * @param v a V, the vertex into which the edges come |
---|
| 210 | * @return a collection of outgoing edges |
---|
[b67a7ac] | 211 | */ |
---|
[2d45fd4] | 212 | public Collection<E> backwardEdges(Graph<V, E> g, V v) { |
---|
| 213 | return g.getInEdges(v); |
---|
| 214 | } |
---|
[b67a7ac] | 215 | /** |
---|
[2d45fd4] | 216 | * Return the source of this edge as defined by the underlying graph. |
---|
| 217 | * @param g the Graph<V, E> being traversed |
---|
| 218 | * @param e an E, the edge being considered |
---|
| 219 | * @return the source of this edge |
---|
[b67a7ac] | 220 | */ |
---|
[2d45fd4] | 221 | public V getSource(Graph<V, E> g, E e) { |
---|
| 222 | return g.getSource(e); |
---|
| 223 | } |
---|
| 224 | /** |
---|
| 225 | * Return the destination of this edge as defined by the underlying |
---|
| 226 | * graph. |
---|
| 227 | * @param g the Graph<V, E> being traversed |
---|
| 228 | * @param e an E, the edge being considered |
---|
| 229 | * @return the destination of this edge |
---|
| 230 | */ |
---|
| 231 | public V getDest(Graph<V, E> g, E e) { |
---|
| 232 | return g.getDest(e); |
---|
| 233 | } |
---|
| 234 | /** |
---|
| 235 | * Called when an edge is added; do nothing. |
---|
| 236 | * @param e an E edge being added. |
---|
| 237 | */ |
---|
| 238 | public void keptEdge(E e) { } |
---|
[31b67d5] | 239 | } |
---|
| 240 | |
---|
| 241 | /** |
---|
[2d45fd4] | 242 | * Class that runs the BFS along reversed links. |
---|
[31b67d5] | 243 | */ |
---|
[2d45fd4] | 244 | private class reverseSearch<V,E> extends bfsSearchDirection<V,E> { |
---|
[b67a7ac] | 245 | /** |
---|
[2d45fd4] | 246 | * Return a collection of outgoing edges; outgoing is reversed with |
---|
| 247 | * respect to the graph. |
---|
| 248 | * @param g the Graph<V, E> being traversed |
---|
| 249 | * @param v a V, the vertex from which the edges come |
---|
| 250 | * @return a collection of outgoing edges |
---|
[b67a7ac] | 251 | */ |
---|
[2d45fd4] | 252 | public Collection<E> forwardEdges(Graph<V, E> g, V v) { |
---|
| 253 | return g.getInEdges(v); |
---|
| 254 | } |
---|
[b67a7ac] | 255 | /** |
---|
[2d45fd4] | 256 | * Return a collection of incoming edges; incoming is reversed with |
---|
| 257 | * respect to the graph. |
---|
| 258 | * @param g the Graph<V, E> being traversed |
---|
| 259 | * @param v a V, the vertex into which the edges come |
---|
| 260 | * @return a collection of outgoing edges |
---|
[b67a7ac] | 261 | */ |
---|
[2d45fd4] | 262 | public Collection<E> backwardEdges(Graph<V, E> g, V v) { |
---|
| 263 | return g.getOutEdges(v); |
---|
| 264 | } |
---|
[b67a7ac] | 265 | /** |
---|
[2d45fd4] | 266 | * Return the source of this edge in the reverse of the underlying |
---|
| 267 | * graph. |
---|
| 268 | * @param g the Graph<V, E> being traversed |
---|
| 269 | * @param e an E, the edge being considered |
---|
| 270 | * @return the source of this edge |
---|
[b67a7ac] | 271 | */ |
---|
[2d45fd4] | 272 | public V getSource(Graph<V, E> g, E e) { |
---|
| 273 | return g.getDest(e); |
---|
| 274 | } |
---|
| 275 | /** |
---|
| 276 | * Return the destination of this edge in the reverse of the underlying |
---|
| 277 | * graph. |
---|
| 278 | * @param g the Graph<V, E> being traversed |
---|
| 279 | * @param e an E, the edge being considered |
---|
| 280 | * @return the destination of this edge |
---|
| 281 | */ |
---|
| 282 | public V getDest(Graph<V, E> g, E e) { |
---|
| 283 | return g.getSource(e); |
---|
| 284 | } |
---|
[31b67d5] | 285 | } |
---|
| 286 | |
---|
| 287 | /** |
---|
[2d45fd4] | 288 | * Class that traverses the graph in reverse asd collects all principal |
---|
| 289 | * nodes found. |
---|
[31b67d5] | 290 | */ |
---|
[2d45fd4] | 291 | private class principalSearch<V, E> |
---|
| 292 | extends reverseSearch<V, E> { |
---|
| 293 | /** This collects the principals */ |
---|
| 294 | private Set<Role> p; |
---|
[b67a7ac] | 295 | /** |
---|
[2d45fd4] | 296 | * Create a principalSearch that inserts into s |
---|
| 297 | * @param s a Set of Roles to update (may be null). |
---|
[b67a7ac] | 298 | */ |
---|
[2d45fd4] | 299 | public principalSearch(Set<Role> s) { |
---|
| 300 | if ( (p = s) == null ) p = new HashSet<Role>(); |
---|
| 301 | } |
---|
[b67a7ac] | 302 | /** |
---|
[2d45fd4] | 303 | * Put principals on valid edges into the attached set. |
---|
| 304 | * @param e an E (edge) being inserted into the final proof graph |
---|
[b67a7ac] | 305 | */ |
---|
[2d45fd4] | 306 | public void keptEdge(E e) { |
---|
| 307 | if ( !(e instanceof Credential) ) return; |
---|
[31b67d5] | 308 | |
---|
[2d45fd4] | 309 | Credential c = (Credential) e; |
---|
| 310 | if (c.tail().is_principal()) p.add(c.tail()); |
---|
| 311 | } |
---|
[31b67d5] | 312 | } |
---|
| 313 | |
---|
| 314 | /** |
---|
[2d45fd4] | 315 | * Conduct a breadth first search along the underlying graph from src to |
---|
| 316 | * dest. If a valid path is found, add it to path (which mat be null). |
---|
| 317 | * The dir parameter controls the direction of the traversal and may invoke |
---|
| 318 | * a side effect when an edge is added to path. Though the graph may be |
---|
| 319 | * traversed in either direction, path is always in the same sense as the |
---|
| 320 | * underlying graph. If dest is null the bfs runs until all |
---|
| 321 | * reachable nodes have been visited and that graph is returned as a |
---|
| 322 | * success. |
---|
| 323 | * @param src the Role to start the search from |
---|
| 324 | * @param dest the Role to find a path to |
---|
| 325 | * @param path a Graph<Role, Credential> in which the result path is |
---|
| 326 | * returned |
---|
| 327 | * @param dir a bfsSearchDirection<Role, Credential> that defines direction |
---|
| 328 | * of search and side effects |
---|
| 329 | * @return true if the path is found |
---|
[b67a7ac] | 330 | */ |
---|
[2d45fd4] | 331 | private boolean bfs(Role src, Role dest, |
---|
| 332 | Graph<Role, Credential> path, |
---|
| 333 | bfsSearchDirection<Role, Credential> dir) { |
---|
| 334 | Queue<Role> q = new ArrayDeque<Role>(); |
---|
| 335 | DirectedGraph<Role, Credential> bfs = |
---|
| 336 | new DirectedSparseGraph<Role, Credential>(); |
---|
| 337 | HashSet<Role> visited = new HashSet<Role>(); |
---|
| 338 | boolean foundDest = false; |
---|
| 339 | |
---|
| 340 | if (src == null || !g.containsVertex(src)) |
---|
| 341 | return false; |
---|
| 342 | |
---|
| 343 | q.add(src); |
---|
| 344 | visited.add(src); |
---|
| 345 | bfs.addVertex(src); |
---|
| 346 | |
---|
| 347 | while ( q.size() > 0 && !foundDest) { |
---|
| 348 | Role x = q.remove(); |
---|
| 349 | for (Credential e: dir.forwardEdges(g, x)) { |
---|
| 350 | Role y = dir.getDest(g, e); |
---|
| 351 | if ( !visited.contains(y)) { |
---|
| 352 | visited.add(y); |
---|
| 353 | bfs.addVertex(y); |
---|
| 354 | bfs.addEdge(e, e.tail(), e.head()); |
---|
| 355 | if ( dest != null && y.equals(dest)) { |
---|
| 356 | foundDest = true; |
---|
| 357 | break; |
---|
| 358 | } |
---|
| 359 | q.add(y); |
---|
| 360 | } |
---|
| 361 | } |
---|
| 362 | } |
---|
| 363 | /* If foundDest is true, we know that the path from src to dest is |
---|
| 364 | * there, so backtrack from dest to source, adding to path as we go. |
---|
| 365 | * If we didn't find anything return the BFS as a partial proof. Note |
---|
| 366 | * that whatever direction the search progresses, this builds a return |
---|
| 367 | * graph that has edges directed the same way as edges in g. |
---|
| 368 | * |
---|
| 369 | * If path is null, don't add the nodes to it (duh) but do decide which |
---|
| 370 | * nodes would be kept in case dir needs that info. E.g. |
---|
| 371 | * findPrincipals does not need the path to the principals, just their |
---|
| 372 | * identity. |
---|
| 373 | */ |
---|
| 374 | if ( foundDest ) { |
---|
| 375 | Role v = dest; |
---|
| 376 | if (path != null && !path.containsVertex(dest)) |
---|
| 377 | path.addVertex(dest); |
---|
| 378 | |
---|
| 379 | while (!v.equals(src)) { |
---|
| 380 | Collection<Credential> backEdges = dir.backwardEdges(bfs, v); |
---|
| 381 | |
---|
| 382 | if (backEdges.size() != 1 ) |
---|
| 383 | System.err.println("What!?"); |
---|
| 384 | for (Credential e: backEdges) { |
---|
| 385 | Role u = dir.getSource(bfs, e); |
---|
| 386 | |
---|
| 387 | if ( path != null && !path.containsVertex(u)) |
---|
| 388 | path.addVertex(u); |
---|
| 389 | if ( path != null && !path.containsEdge(e)) |
---|
| 390 | path.addEdge(e, e.tail(), e.head()); |
---|
| 391 | dir.keptEdge(e); |
---|
| 392 | v = u; |
---|
| 393 | } |
---|
| 394 | } |
---|
| 395 | } else { |
---|
| 396 | for (Role v: bfs.getVertices() ) |
---|
| 397 | if (path != null && !path.containsVertex(v)) |
---|
| 398 | path.addVertex(v); |
---|
| 399 | for (Credential e: bfs.getEdges()) { |
---|
| 400 | if ( path != null && !path.containsEdge(e)) |
---|
| 401 | path.addEdge(e, e.tail(), e.head()); |
---|
| 402 | dir.keptEdge(e); |
---|
| 403 | } |
---|
| 404 | } |
---|
| 405 | return foundDest || dest == null; |
---|
[31b67d5] | 406 | } |
---|
| 407 | } |
---|