def access_control(self, action, support=''): """Event handler for making access_control request. ACTION is an atom describing a proposed action instance. SUPPORT is any data that should be assumed true when posing the query. Returns True iff access is granted. """ # parse if isinstance(action, basestring): action = self.parse1(action) assert compile.is_atom(action), "ACTION must be an atom" if isinstance(support, basestring): support = self.parse(support) # add support to theory newth = NonrecursiveRuleTheory(abbr="Temp") newth.tracer.trace('*') for form in support: newth.insert(form) acth = self.theory[self.ACCESSCONTROL_THEORY] acth.includes.append(newth) # check if action is true in theory result = len(acth.select(action, find_all=False)) > 0 # allow new theory to be freed acth.includes.remove(newth) return result
def __contains__(self, formula): if not compile.is_atom(formula): return False if formula.table not in self.data: return False event_data = self.data[formula.table] raw_tuple = tuple(formula.argument_names()) return any((dbtuple.tuple == raw_tuple for dbtuple in event_data))
def remediate_obj(self, formula): """Find a collection of action invocations That if executed result in FORMULA becoming false. """ actionth = self.theory[self.ACTION_THEORY] classifyth = self.theory[self.CLASSIFY_THEORY] # look at FORMULA if compile.is_atom(formula): pass # TODO(tim): clean up unused variable # output = formula elif compile.is_regular_rule(formula): pass # TODO(tim): clean up unused variable # output = formula.head else: assert False, "Must be a formula" # grab a single proof of FORMULA in terms of the base tables base_tables = classifyth.base_tables() proofs = classifyth.explain(formula, base_tables, False) if proofs is None: # FORMULA already false; nothing to be done return [] # Extract base table literals that make that proof true. # For remediation, we assume it suffices to make any of those false. # (Leaves of proof may not be literals or may not be written in # terms of base tables, despite us asking for base tables-- # because of negation.) leaves = [leaf for leaf in proofs[0].leaves() if (compile.is_atom(leaf) and leaf.table in base_tables)] self.table_log(None, "Leaves: %s", iterstr(leaves)) # Query action theory for abductions of negated base tables actions = self.get_action_names() results = [] for lit in leaves: goal = lit.make_positive() if lit.is_negated(): goal.table = goal.table + "+" else: goal.table = goal.table + "-" # return is a list of goal :- act1, act2, ... # This is more informative than query :- act1, act2, ... for abduction in actionth.abduce(goal, actions, False): results.append(abduction) return results
def explain(self, query, tablenames, find_all): """Returns a list of proofs if QUERY is true or None if else.""" assert compile.is_atom(query), "Explain requires an atom" # ignoring TABLENAMES and FIND_ALL # except that we return the proper type. proof = self.explain_aux(query, 0) if proof is None: return None else: return [proof]
def update_would_cause_errors(self, events): """Return a list of compile.CongressException. Return a list of compile.CongressException if we were to apply the events EVENTS to the current policy. """ self.log(None, "update_would_cause_errors %s", iterstr(events)) errors = [] for event in events: if not compile.is_atom(event.formula): errors.append(compile.CongressException( "Non-atomic formula is not permitted: {}".format( str(event.formula)))) else: errors.extend(compile.fact_errors( event.formula, self.theories, self.name)) return errors
def delete_actual(self, atom, proofs=None): """Workhorse for deleting ATOM from the DB. Along with the proofs that are no longer true. """ assert compile.is_atom(atom), "Delete requires Atom" self.log(atom.table, "Delete: %s", atom) table, dbtuple = self.atom_to_internal(atom, proofs) if table not in self.data: return for i in xrange(0, len(self.data[table])): existingtuple = self.data[table][i] if existingtuple.tuple == dbtuple.tuple: existingtuple.proofs -= dbtuple.proofs if len(existingtuple.proofs) == 0: del self.data[table][i] return
def execute_obj(self, actions): """Executes the list of ACTION instances one at a time. For now, our execution is just logging. """ LOG.debug("Executing: %s", iterstr(actions)) assert all(compile.is_atom(action) and action.is_ground() for action in actions) action_names = self.get_action_names() assert all(action.table in action_names for action in actions) for action in actions: if not action.is_ground(): if self.logger is not None: self.logger.warn("Unground action to execute: %s", action) continue if self.logger is not None: self.logger.info("%s", action)
def modify(self, event): """Insert/Delete atom. Inserts/deletes ATOM and returns a list of changes that were caused. That list contains either 0 or 1 Event. """ assert compile.is_atom(event.formula), "Modify requires Atom" atom = event.formula self.log(atom.table, "Modify: %s", atom) if self.is_noop(event): self.log(atom.table, "Event %s is a noop", event) return [] if event.insert: self.insert_actual(atom, proofs=event.proofs) else: self.delete_actual(atom, proofs=event.proofs) return [event]
def compute_route(self, events, theory): """Compute rerouting. When a formula is inserted/deleted (in OPERATION) into a THEORY, it may need to be rerouted to another theory. This function computes that rerouting. Returns a Theory object. """ self.table_log(None, "Computing route for theory %s and events %s", theory.name, iterstr(events)) # Since Enforcement includes Classify and Classify includes Database, # any operation on data needs to be funneled into Enforcement. # Enforcement pushes it down to the others and then # reacts to the results. That is, we really have one big theory # Enforcement + Classify + Database as far as the data is concerned # but formulas can be inserted/deleted into each policy individually. if all([compile.is_atom(event.formula) for event in events]): if (theory is self.theory[self.CLASSIFY_THEORY] or theory is self.theory[self.DATABASE]): return self.theory[self.ENFORCEMENT_THEORY] return theory
def select(self, query, find_all=True): """Return list of instances of QUERY that are true. If FIND_ALL is False, the return list has at most 1 element. """ assert compile.is_datalog(query), "Query must be atom/rule" if compile.is_atom(query): literals = [query] else: literals = query.body # Because our output is instances of QUERY, need all the variables # in QUERY. bindings = self.top_down_evaluation(query.variables(), literals, find_all=find_all) # LOG.debug("Top_down_evaluation returned: %s", bindings) if len(bindings) > 0: self.log( query.tablename(), "Found answer %s", "[" + ",".join([str(query.plug(x)) for x in bindings]) + "]") return [query.plug(x) for x in bindings]
def insert_actual(self, atom, proofs=None): """Workhorse for inserting ATOM into the DB. Along with proofs explaining how ATOM was computed from other tables. """ assert compile.is_atom(atom), "Insert requires Atom" table, dbtuple = self.atom_to_internal(atom, proofs) self.log(table, "Insert: %s", atom) if table not in self.data: self.data[table] = [dbtuple] self.log(atom.table, "First tuple in table %s", table) return else: for existingtuple in self.data[table]: assert existingtuple.proofs is not None if existingtuple.tuple == dbtuple.tuple: assert existingtuple.proofs is not None existingtuple.proofs |= dbtuple.proofs assert existingtuple.proofs is not None return self.data[table].append(dbtuple)
def receive_data_update(self, msg): """Handler for when dataservice publishes a delta.""" self.log("received update data msg for %s: %s", msg.header['dataindex'], runtime.iterstr(msg.body.data)) events = msg.body.data for event in events: assert compile.is_atom(event.formula), \ "receive_data_update received non-atom: " + str(event.formula) # prefix tablename with data source event.formula.table = msg.replyTo + ":" + event.formula.table (permitted, changes) = self.update(events) if not permitted: raise runtime.CongressRuntime( "Update not permitted." + '\n'.join(str(x) for x in changes)) else: dataindex = msg.header['dataindex'] tablename = msg.replyTo + ":" + dataindex self.log("update data msg for %s caused %d changes: %s", tablename, len(changes), runtime.iterstr(changes)) if tablename in self.theory['classification'].tablenames(): rows = self.theory['classification'].content([tablename]) self.log("current table: %s", runtime.iterstr(rows))
def abduce(self, query, tablenames, find_all=True): """Compute additional literals. Computes additional literals that if true would make (some instance of) QUERY true. Returns a list of rules where the head represents an instance of the QUERY and the body is the collection of literals that must be true in order to make that instance true. If QUERY is a rule, each result is an instance of the head of that rule, and the computed literals if true make the body of that rule (and hence the head) true. If FIND_ALL is true, the return list has at most one element. Limitation: every negative literal relevant to a proof of QUERY is unconditionally true, i.e. no literals are saved when proving a negative literal is true. """ assert compile.is_datalog(query), "Explain requires a formula" if compile.is_atom(query): literals = [query] output = query else: literals = query.body output = query.head # We need all the variables we will be using in the output, which # here is just the head of QUERY (or QUERY itself if it is an atom) abductions = self.top_down_abduction( output.variables(), literals, find_all=find_all, save=lambda lit, binding: lit.table in tablenames) results = [ compile.Rule(output.plug(abd.binding), abd.support) for abd in abductions ] self.log(query.tablename(), "abduction result:") self.log(query.tablename(), "\n".join([str(x) for x in results])) return results
def receive_data_update(self, msg): """Handler for when dataservice publishes a delta.""" self.log("received update data msg for %s: %s", msg.header['dataindex'], runtime.iterstr(msg.body.data)) events = msg.body.data for event in events: assert compile.is_atom( event.formula), ("receive_data_update received non-atom: " + str(event.formula)) # prefix tablename with data source event.target = msg.replyTo (permitted, changes) = self.update(events) if not permitted: raise runtime.CongressRuntime("Update not permitted." + '\n'.join(str(x) for x in changes)) else: tablename = msg.header['dataindex'] service = msg.replyTo self.log("update data msg for %s from %s caused %d " "changes: %s", tablename, service, len(changes), runtime.iterstr(changes)) if tablename in self.theory[service].tablenames(): rows = self.theory[service].content([tablename]) self.log("current table: %s", runtime.iterstr(rows))
def delete_actual(self, rule): """Delete RULE and return True if there was a change.""" if compile.is_atom(rule): rule = compile.Rule(rule, [], rule.location) self.log(rule.head.table, "Delete: %s", rule) return self.rules.discard_rule(rule.head.table, rule)
def insert_actual(self, rule): """Insert RULE and return True if there was a change.""" if compile.is_atom(rule): rule = compile.Rule(rule, [], rule.location) self.log(rule.head.table, "Insert: %s", rule) return self.rules.add_rule(rule.head.table, rule)
def test_type_checkers(self): """Test the type checkers, e.g. is_atom, is_rule.""" atom = compile.Literal("p", []) atom2 = compile.Literal("q", []) atom3 = compile.Literal("r", []) lit = compile.Literal("r", [], negated=True) regular_rule = compile.Rule(atom, [atom2, atom3]) regular_rule2 = compile.Rule(atom, [lit, atom2]) multi_rule = compile.Rule([atom, atom2], [atom3]) fake_rule = compile.Rule([atom, 1], [atom2]) fake_rule2 = compile.Rule(atom, [atom2, 1]) # is_atom self.assertTrue(compile.is_atom(atom)) self.assertTrue(compile.is_atom(atom2)) self.assertTrue(compile.is_atom(atom3)) self.assertFalse(compile.is_atom(lit)) self.assertFalse(compile.is_atom(regular_rule)) self.assertFalse(compile.is_atom(regular_rule2)) self.assertFalse(compile.is_atom(multi_rule)) self.assertFalse(compile.is_atom(fake_rule)) self.assertFalse(compile.is_atom(fake_rule2)) self.assertFalse(compile.is_atom("a string")) # is_literal self.assertTrue(compile.is_literal(atom)) self.assertTrue(compile.is_literal(atom2)) self.assertTrue(compile.is_literal(atom3)) self.assertTrue(compile.is_literal(lit)) self.assertFalse(compile.is_literal(regular_rule)) self.assertFalse(compile.is_literal(regular_rule2)) self.assertFalse(compile.is_literal(multi_rule)) self.assertFalse(compile.is_literal(fake_rule)) self.assertFalse(compile.is_literal(fake_rule2)) self.assertFalse(compile.is_literal("a string")) # is_regular_rule self.assertFalse(compile.is_regular_rule(atom)) self.assertFalse(compile.is_regular_rule(atom2)) self.assertFalse(compile.is_regular_rule(atom3)) self.assertFalse(compile.is_regular_rule(lit)) self.assertTrue(compile.is_regular_rule(regular_rule)) self.assertTrue(compile.is_regular_rule(regular_rule2)) self.assertFalse(compile.is_regular_rule(multi_rule)) self.assertFalse(compile.is_regular_rule(fake_rule)) self.assertFalse(compile.is_regular_rule(fake_rule2)) self.assertFalse(compile.is_regular_rule("a string")) # is_multi_rule self.assertFalse(compile.is_multi_rule(atom)) self.assertFalse(compile.is_multi_rule(atom2)) self.assertFalse(compile.is_multi_rule(atom3)) self.assertFalse(compile.is_multi_rule(lit)) self.assertFalse(compile.is_multi_rule(regular_rule)) self.assertFalse(compile.is_multi_rule(regular_rule2)) self.assertTrue(compile.is_multi_rule(multi_rule)) self.assertFalse(compile.is_multi_rule(fake_rule)) self.assertFalse(compile.is_multi_rule(fake_rule2)) self.assertFalse(compile.is_multi_rule("a string")) # is_rule self.assertFalse(compile.is_rule(atom)) self.assertFalse(compile.is_rule(atom2)) self.assertFalse(compile.is_rule(atom3)) self.assertFalse(compile.is_rule(lit)) self.assertTrue(compile.is_rule(regular_rule)) self.assertTrue(compile.is_rule(regular_rule2)) self.assertTrue(compile.is_rule(multi_rule)) self.assertFalse(compile.is_rule(fake_rule)) self.assertFalse(compile.is_rule(fake_rule2)) self.assertFalse(compile.is_rule("a string")) # is_datalog self.assertTrue(compile.is_datalog(atom)) self.assertTrue(compile.is_datalog(atom2)) self.assertTrue(compile.is_datalog(atom3)) self.assertFalse(compile.is_datalog(lit)) self.assertTrue(compile.is_datalog(regular_rule)) self.assertTrue(compile.is_datalog(regular_rule2)) self.assertFalse(compile.is_datalog(multi_rule)) self.assertFalse(compile.is_datalog(fake_rule)) self.assertFalse(compile.is_datalog(fake_rule2)) self.assertFalse(compile.is_datalog("a string")) # is_extended_datalog self.assertTrue(compile.is_extended_datalog(atom)) self.assertTrue(compile.is_extended_datalog(atom2)) self.assertTrue(compile.is_extended_datalog(atom3)) self.assertFalse(compile.is_extended_datalog(lit)) self.assertTrue(compile.is_extended_datalog(regular_rule)) self.assertTrue(compile.is_extended_datalog(regular_rule2)) self.assertTrue(compile.is_extended_datalog(multi_rule)) self.assertFalse(compile.is_extended_datalog(fake_rule)) self.assertFalse(compile.is_extended_datalog(fake_rule2)) self.assertFalse(compile.is_extended_datalog("a string"))