blob: 6d6611788efd69c3d62f86d217f7219fca00d16a [file] [log] [blame]
Matteo Scandolod2044a42017-08-07 16:08:28 -07001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15
Zack Williams045b63d2019-01-22 16:30:57 -070016from __future__ import print_function
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040017import astunparse
18import ast
19import random
20import string
Sapan Bhatia5ea307d2017-07-19 00:13:21 -040021import jinja2
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040022from plyxproto.parser import *
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040023
Zack Williams045b63d2019-01-22 16:30:57 -070024BINOPS = ["|", "&", "->"]
25QUANTS = ["exists", "forall"]
26
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040027
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040028class PolicyException(Exception):
29 pass
30
Zack Williams045b63d2019-01-22 16:30:57 -070031
Sapan Bhatiab69f4702017-07-31 16:03:33 -040032class ConstructNotHandled(Exception):
33 pass
34
Zack Williams045b63d2019-01-22 16:30:57 -070035
Sapan Bhatiab69f4702017-07-31 16:03:33 -040036class TrivialPolicy(Exception):
37 pass
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040038
Zack Williams045b63d2019-01-22 16:30:57 -070039
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040040class AutoVariable:
41 def __init__(self, base):
42 self.base = base
43
44 def __iter__(self):
45 self.idx = 0
46 return self
47
48 def next(self):
Zack Williams045b63d2019-01-22 16:30:57 -070049 var = "i%d" % self.idx
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040050 self.idx += 1
51 return var
52
Zack Williams045b63d2019-01-22 16:30:57 -070053
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040054def gen_random_string():
Zack Williams045b63d2019-01-22 16:30:57 -070055 return "".join(
56 random.choice(string.ascii_lowercase + string.digits) for _ in range(5)
57 )
58
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040059
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040060class FOL2Python:
61 def __init__(self, context_map=None):
62 # This will produce i0, i1, i2 etc.
Zack Williams045b63d2019-01-22 16:30:57 -070063 self.loopvar = iter(AutoVariable("i"))
64 self.verdictvar = iter(AutoVariable("result"))
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040065
66 self.loop_variable = self.loopvar.next()
67 self.verdict_variable = self.verdictvar.next()
68 self.context_map = context_map
69
70 if not self.context_map:
Zack Williams045b63d2019-01-22 16:30:57 -070071 self.context_map = {"user": "self", "obj": "obj"}
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040072
73 def loop_next(self):
74 self.loop_variable = self.loopvar.next()
75
76 def verdict_next(self):
77 self.verdict_variable = self.verdictvar.next()
78
79 def gen_enumerate(self, fol):
80 pass
81
82 def format_term_for_query(self, model, term, django=False):
Zack Williams045b63d2019-01-22 16:30:57 -070083 if term.startswith(model + "."):
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040084 term = term[len(model) + 1:]
85 if django:
Zack Williams045b63d2019-01-22 16:30:57 -070086 term = term.replace(".", "__")
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040087 else:
Zack Williams045b63d2019-01-22 16:30:57 -070088 term = "__elt" + "." + term
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040089 return term
90
91 def fol_to_python_filter(self, model, e, django=False, negate=False):
92 try:
93 (k, v), = e.items()
94 except AttributeError:
95 return [self.format_term_for_query(model, e)]
96
97 if django:
98 if negate:
99 # De Morgan's negation
Zack Williams045b63d2019-01-22 16:30:57 -0700100 q_bracket = "~Q(%s)"
101 or_expr = ","
102 and_expr = "|"
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400103 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700104 q_bracket = "Q(%s)"
105 or_expr = "|"
106 and_expr = ","
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400107 else:
108 if negate:
109 # De Morgan's negation
Zack Williams045b63d2019-01-22 16:30:57 -0700110 q_bracket = "not %s"
111 or_expr = " and "
112 and_expr = " or "
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400113 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700114 q_bracket = "%s"
115 or_expr = " or "
116 and_expr = " and "
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400117
Zack Williams045b63d2019-01-22 16:30:57 -0700118 if k in ["=", "in"]:
119 v = [self.format_term_for_query(model, term, django=django) for term in v]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400120 if django:
Zack Williams045b63d2019-01-22 16:30:57 -0700121 operator_map = {"=": " = ", "in": "__in"}
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400122 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700123 operator_map = {"=": " == ", "in": "in"}
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400124 operator = operator_map[k]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400125 return [q_bracket % operator.join(v)]
Zack Williams045b63d2019-01-22 16:30:57 -0700126 elif k == "|":
127 components = [
128 self.fol_to_python_filter(model, x, django=django).pop() for x in v
129 ]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400130 return [or_expr.join(components)]
Zack Williams045b63d2019-01-22 16:30:57 -0700131 elif k == "&":
132 components = [
133 self.fol_to_python_filter(model, x, django=django).pop() for x in v
134 ]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400135 return [and_expr.join(components)]
Zack Williams045b63d2019-01-22 16:30:57 -0700136 elif k == "->":
137 components = [
138 self.fol_to_python_filter(model, x, django=django).pop() for x in v
139 ]
140 return ["~%s | %s" % (components[0], components[1])]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400141
142 """ Convert a single leaf node from a string
143 to an AST"""
Zack Williams045b63d2019-01-22 16:30:57 -0700144
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400145 def str_to_ast(self, s):
146 ast_module = ast.parse(s)
147 return ast_module.body[0]
148
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400149 def reduce_operands(self, operands):
Zack Williams045b63d2019-01-22 16:30:57 -0700150 if operands[0] in ["True", "False"]:
151 return (operands[0], operands[1])
152 elif operands[1] in ["True", "False"]:
153 return (operands[1], operands[0])
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400154 else:
155 return None
156
157 """ Simplify binops with constants """
Zack Williams045b63d2019-01-22 16:30:57 -0700158
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400159 def simplify_binop(self, binop):
Zack Williams045b63d2019-01-22 16:30:57 -0700160 (k, v), = binop.items()
161 if k == "->":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400162 lhs, rhs = v
Zack Williams045b63d2019-01-22 16:30:57 -0700163 if lhs == "True":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400164 return rhs
Zack Williams045b63d2019-01-22 16:30:57 -0700165 elif rhs == "True":
166 return "True"
167 elif lhs == "False":
168 return "True"
169 elif rhs == "False":
170 return {"not": lhs}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400171
172 var_expr = self.reduce_operands(v)
173
Zack Williams045b63d2019-01-22 16:30:57 -0700174 if not var_expr:
175 return binop
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400176 else:
177 constant, var = var_expr
Zack Williams045b63d2019-01-22 16:30:57 -0700178 if k == "|":
179 if constant == "True":
180 return "True"
181 elif constant == "False":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400182 return var
183 else:
184 raise Exception("Internal error - variable read as constant")
Zack Williams045b63d2019-01-22 16:30:57 -0700185 elif k == "&":
186 if constant == "True":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400187 return var
Zack Williams045b63d2019-01-22 16:30:57 -0700188 elif constant == "False":
189 return "False"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400190
191 def is_constant(self, var, fol):
192 try:
193 (k, v), = fol.items()
194 except AttributeError:
Zack Williams045b63d2019-01-22 16:30:57 -0700195 k = "term"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400196 v = fol
Zack Williams045b63d2019-01-22 16:30:57 -0700197
198 if k in ["python", "policy"]:
199 # Treat as a constant and hoist, since it cannot be quantified
200 return True
201 elif k == "term":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400202 return not v.startswith(var)
Zack Williams045b63d2019-01-22 16:30:57 -0700203 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400204 return self.is_constant(var, fol)
Zack Williams045b63d2019-01-22 16:30:57 -0700205 elif k in ["in", "="]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400206 lhs, rhs = v
Zack Williams045b63d2019-01-22 16:30:57 -0700207 return self.is_constant(var, lhs) and self.is_constant(var, rhs)
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400208 elif k in BINOPS:
209 lhs, rhs = v
210 return self.is_constant(lhs, var) and self.is_constant(rhs, var)
211 elif k in QUANTS:
212 is_constant = self.is_constant(var, fol[1])
213 return is_constant
214 else:
215 raise ConstructNotHandled(k)
216
217 def find_constants(self, var, fol, constants):
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400218 try:
219 (k, v), = fol.items()
220 except AttributeError:
Zack Williams045b63d2019-01-22 16:30:57 -0700221 k = "term"
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400222 v = fol
223
Zack Williams045b63d2019-01-22 16:30:57 -0700224 if k in ["python", "policy"]:
225 # Treat as a constant and hoist, since it cannot be quantified
226 if fol not in constants:
227 constants.append(fol)
228 return constants
229 elif k == "term":
230 if not v.startswith(var):
231 constants.append(v)
232 return constants
233 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400234 return self.find_constants(var, v, constants)
Zack Williams045b63d2019-01-22 16:30:57 -0700235 elif k in ["in", "="]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400236 lhs, rhs = v
237 if isinstance(lhs, str) and isinstance(rhs, str):
238 if not lhs.startswith(var) and not rhs.startswith(var):
239 constants.append(fol)
240 return constants
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400241 else:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400242 constants = self.find_constants(var, lhs, constants)
243 return self.find_constants(var, rhs, constants)
244 elif k in BINOPS:
245 lhs, rhs = v
246 constants = self.find_constants(var, lhs, constants)
247 constants = self.find_constants(var, rhs, constants)
248 return constants
249 elif k in QUANTS:
250 is_constant = self.is_constant(var, v[1])
Zack Williams045b63d2019-01-22 16:30:57 -0700251 if is_constant:
252 constants.append(fol)
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400253 return constants
254 else:
255 raise ConstructNotHandled(k)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400256
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400257 """ Hoist constants out of quantifiers. Depth-first. """
Zack Williams045b63d2019-01-22 16:30:57 -0700258
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400259 def hoist_outer(self, fol):
260 try:
261 (k, v), = fol.items()
262 except AttributeError:
Zack Williams045b63d2019-01-22 16:30:57 -0700263 k = "term"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400264 v = fol
265
Zack Williams045b63d2019-01-22 16:30:57 -0700266 if k in ["python", "policy"]:
267 # Tainted, optimization and distribution not possible
268 return fol
269 elif k == "term":
270 return fol
271 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400272 vprime = self.hoist_outer(v)
Zack Williams045b63d2019-01-22 16:30:57 -0700273 return {"not": vprime}
274 elif k in ["in", "="]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400275 lhs, rhs = v
276 rlhs = self.hoist_outer(lhs)
277 rrhs = self.hoist_outer(rhs)
Zack Williams045b63d2019-01-22 16:30:57 -0700278 return {k: [rlhs, rrhs]}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400279 elif k in BINOPS:
280 lhs, rhs = v
281 rlhs = self.hoist_outer(lhs)
282 rrhs = self.hoist_outer(rhs)
283
284 fol_prime = {k: [rlhs, rrhs]}
285 fol_simplified = self.simplify_binop(fol_prime)
286 return fol_simplified
287 elif k in QUANTS:
288 rexpr = self.hoist_outer(v[1])
Zack Williams045b63d2019-01-22 16:30:57 -0700289 return self.hoist_quant(k, [v[0], rexpr])
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400290 else:
291 raise ConstructNotHandled(k)
292
293 def replace_const(self, fol, c, value):
294 if fol == c:
295 return value
296
297 try:
298 (k, v), = fol.items()
299 except AttributeError:
Zack Williams045b63d2019-01-22 16:30:57 -0700300 k = "term"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400301 v = fol
302
Zack Williams045b63d2019-01-22 16:30:57 -0700303 if k == "term":
304 if v == c:
305 return value
306 else:
307 return v
308 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400309 new_expr = self.replace_const(v, c, value)
Zack Williams045b63d2019-01-22 16:30:57 -0700310 if new_expr == "True":
311 return "False"
312 elif new_expr == "False":
313 return "True"
314 else:
315 return {"not": new_expr}
316 elif k in ["in", "="]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400317 lhs, rhs = v
318 rlhs = self.replace_const(lhs, c, value)
319 rrhs = self.replace_const(rhs, c, value)
320
Zack Williams045b63d2019-01-22 16:30:57 -0700321 if rlhs == rrhs:
322 return "True"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400323 else:
324 return {k: [rlhs, rrhs]}
325 elif k in BINOPS:
326 lhs, rhs = v
327
328 rlhs = self.replace_const(lhs, c, value)
329 rrhs = self.replace_const(rhs, c, value)
Zack Williams045b63d2019-01-22 16:30:57 -0700330
331 return self.simplify_binop({k: [rlhs, rrhs]})
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400332 elif k in QUANTS:
333 var, expr = v
334 new_expr = self.replace_const(expr, c, value)
Zack Williams045b63d2019-01-22 16:30:57 -0700335 if new_expr in ["True", "False"]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400336 return new_expr
337 else:
338 return {k: [var, new_expr]}
339 else:
340 raise ConstructNotHandled(k)
341
342 def shannon_expand(self, c, fol):
Zack Williams045b63d2019-01-22 16:30:57 -0700343 lhs = self.replace_const(fol, c, "True")
344 rhs = self.replace_const(fol, c, "False")
345 not_c = {"not": c}
346 rlhs = {"&": [c, lhs]}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400347 rlhs = self.simplify_binop(rlhs)
348
Zack Williams045b63d2019-01-22 16:30:57 -0700349 rrhs = {"&": [not_c, rhs]}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400350 rrhs = self.simplify_binop(rrhs)
351
Zack Williams045b63d2019-01-22 16:30:57 -0700352 combined = {"|": [rlhs, rrhs]}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400353 return self.simplify_binop(combined)
354
355 def hoist_quant(self, k, expr):
356 var, v = expr
357
358 constants = self.find_constants(var, v, constants=[])
359
360 fol = {k: expr}
361
362 for c in constants:
363 fol = self.shannon_expand(c, fol)
364
365 return fol
366
367 """
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400368 if var:
369 if k == 'term':
370 if not v.startswith(var):
371 return {'hoist': ['const', fol], 'result': 'True'}
372 else:
373 return {'hoist': [], 'result': fol}
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400374 elif k in ['=', 'in']:
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400375 lhs, rhs = v
376 if not lhs.startswith(var) and not rhs.startswith(var):
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400377 return {'hoist': [k, fol], 'result': 'True'} # XXX
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400378 else:
379 return {'hoist': [], 'result': fol}
380 elif k in BINOPS:
381 lhs, rhs = v
382 rlhs = self.hoist_constants(lhs, var)
383 rrhs = self.hoist_constants(rhs, var)
384
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400385 if rlhs['hoist'] and rrhs['hoist'] and rlhs['result']=='True' and llhs['result']=='True':
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400386 return {'hoist': ['=', fol], 'result': 'True'}
387 elif rlhs['hoist']:
388 return {'hoist': [k, lhs], 'result': rhs}
389 elif rrhs['hoist']:
390 return {'hoist': [k, rhs], 'result': lhs}
391 else:
392 return {'hoist': [], 'result': fol}
393
394 elif k in QUANTS:
395 var2, expr = v
396 result = self.hoist_constants(expr, var2)
397 if result['hoist']:
398 if result['result'] == 'True':
399 return {'hoist': ['const'], 'result': result['hoist'][1]}
400 elif result['hoist'][0] in BINOPS:
401 return {'hoist': ['const'], 'result': {result['hoist'][0]: [result['hoist'][1], {k: [var2, result['result']]}]}}
402 else:
403 return {'hoist': ['const'], 'result': {k: [var2, result['result']]}}
404 else:
405 result = self.hoist_constants(expr, var)
406 if result['result'] == 'True':
407 return {'hoist': ['&', fol], 'result': 'True'}
408 else:
409 return {'hoist': [], 'result': fol}
410 else:
411 return {'hoist': [], 'result': fol}
412 else:
413 if k in BINOPS:
414 lhs, rhs = v
415 rlhs = self.hoist_constants(lhs)
416 rrhs = self.hoist_constants(rhs)
417 return {k: [rlhs, rrhs]}
418 elif k in QUANTS:
419 var, expr = v
420 result = self.hoist_constants(expr, var)
421 if result['hoist']:
422 if result['result'] == 'True':
423 return result['hoist'][1]
424 elif result['hoist'][0] in BINOPS:
425 return {result['hoist'][0]: [result['hoist'][1], {k: [var, result['result']]}]}
426 else:
427 return {k: [var, result['result']]}
428 else:
429 return fol
430 else:
431 return fol
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400432 """
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400433
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400434 def gen_validation_function(self, fol, policy_name, message, tag):
435 if not tag:
436 tag = gen_random_string()
437
Zack Williams045b63d2019-01-22 16:30:57 -0700438 policy_function_name_template = "policy_%s_" + "%(random_string)s" % {
439 "random_string": tag
440 }
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400441 policy_function_name = policy_function_name_template % policy_name
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400442 self.verdict_next()
443 function_str = """
444def %(fn_name)s(obj, ctx):
Scott Bakera8b6d692018-04-17 10:21:32 -0700445 if not %(vvar)s: raise XOSValidationError("%(message)s".format(obj=obj, ctx=ctx))
Zack Williams045b63d2019-01-22 16:30:57 -0700446 """ % {
447 "fn_name": policy_function_name,
448 "vvar": self.verdict_variable,
449 "message": message,
450 }
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400451
452 function_ast = self.str_to_ast(function_str)
Zack Williams045b63d2019-01-22 16:30:57 -0700453 policy_code = self.gen_test(
454 policy_function_name_template, fol, self.verdict_variable
455 )
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400456
457 function_ast.body = [policy_code] + function_ast.body
458
459 return function_ast
460
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400461 def gen_test_function(self, fol, policy_name, tag):
462 if not tag:
463 tag = gen_random_string()
464
Zack Williams045b63d2019-01-22 16:30:57 -0700465 policy_function_name_template = "%s_" + "%(random_string)s" % {
466 "random_string": tag
467 }
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400468 policy_function_name = policy_function_name_template % policy_name
469
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400470 self.verdict_next()
471 function_str = """
472def %(fn_name)s(obj, ctx):
473 return %(vvar)s
Zack Williams045b63d2019-01-22 16:30:57 -0700474 """ % {
475 "fn_name": policy_function_name,
476 "vvar": self.verdict_variable,
477 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400478
479 function_ast = self.str_to_ast(function_str)
Zack Williams045b63d2019-01-22 16:30:57 -0700480 policy_code = self.gen_test(
481 policy_function_name_template, fol, self.verdict_variable
482 )
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400483
484 function_ast.body = [policy_code] + function_ast.body
485
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400486 return function_ast
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400487
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400488 def gen_test(self, fn_template, fol, verdict_var, bindings=None):
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400489 if isinstance(fol, str):
Zack Williams045b63d2019-01-22 16:30:57 -0700490 return self.str_to_ast(
491 "%(verdict_var)s = %(constant)s"
492 % {"verdict_var": verdict_var, "constant": fol}
493 )
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400494
495 (k, v), = fol.items()
496
Zack Williams045b63d2019-01-22 16:30:57 -0700497 if k == "policy":
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400498 policy_name, object_name = v
499
500 policy_fn = fn_template % policy_name
501 call_str = """
Sapan Bhatiac6543dd2017-12-07 11:40:36 -0500502if obj.%(object_name)s:
503 %(verdict_var)s = %(policy_fn)s(obj.%(object_name)s, ctx)
504else:
505 # Everybody has access to null objects
506 %(verdict_var)s = True
Zack Williams045b63d2019-01-22 16:30:57 -0700507 """ % {
508 "verdict_var": verdict_var,
509 "policy_fn": policy_fn,
510 "object_name": object_name,
511 }
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400512
513 call_ast = self.str_to_ast(call_str)
514 return call_ast
Zack Williams045b63d2019-01-22 16:30:57 -0700515 if k == "python":
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400516 try:
517 expr_ast = self.str_to_ast(v)
518 except SyntaxError:
Zack Williams045b63d2019-01-22 16:30:57 -0700519 raise PolicyException("Syntax error in %s" % v)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400520
521 if not isinstance(expr_ast, ast.Expr):
Zack Williams045b63d2019-01-22 16:30:57 -0700522 raise PolicyException("%s is not an expression" % expr_ast)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400523
524 assignment_str = """
525%(verdict_var)s = (%(escape_expr)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700526 """ % {
527 "verdict_var": verdict_var,
528 "escape_expr": v,
529 }
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400530
531 assignment_ast = self.str_to_ast(assignment_str)
532 return assignment_ast
Zack Williams045b63d2019-01-22 16:30:57 -0700533 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400534 top_vvar = verdict_var
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400535 self.verdict_next()
536 sub_vvar = self.verdict_variable
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400537 block = self.gen_test(fn_template, v, sub_vvar)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400538 assignment_str = """
539%(verdict_var)s = not (%(subvar)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700540 """ % {
541 "verdict_var": top_vvar,
542 "subvar": sub_vvar,
543 }
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400544
545 assignment_ast = self.str_to_ast(assignment_str)
546
547 return ast.Module(body=[block, assignment_ast])
Zack Williams045b63d2019-01-22 16:30:57 -0700548 elif k in ["=", "in"]:
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400549 # This is the simplest case, we don't recurse further
550 # To use terms that are not simple variables, use
551 # the Python escape, e.g. {{ slice.creator is not None }}
552 lhs, rhs = v
553
554 assignments = []
555
556 try:
557 for t in lhs, rhs:
Zack Williams045b63d2019-01-22 16:30:57 -0700558 py_expr = t["python"]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400559
560 self.verdict_next()
561 vv = self.verdict_variable
562
563 try:
564 expr_ast = self.str_to_ast(py_expr)
565 except SyntaxError:
Zack Williams045b63d2019-01-22 16:30:57 -0700566 raise PolicyException("Syntax error in %s" % v)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400567
568 if not isinstance(expr_ast, ast.Expr):
Zack Williams045b63d2019-01-22 16:30:57 -0700569 raise PolicyException("%s is not an expression" % expr_ast)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400570
571 assignment_str = """
572%(verdict_var)s = (%(escape_expr)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700573 """ % {
574 "verdict_var": vv,
575 "escape_expr": py_expr,
576 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400577
578 if t == lhs:
579 lhs = vv
580 else:
581 rhs = vv
582
583 assignment_ast = self.str_to_ast(assignment_str)
584 assignments.append(assignment_ast)
585 except TypeError:
586 pass
587
Zack Williams045b63d2019-01-22 16:30:57 -0700588 if k == "=":
589 operator = "=="
590 elif k == "in":
591 operator = "in"
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400592
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400593 comparison_str = """
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400594%(verdict_var)s = (%(lhs)s %(operator)s %(rhs)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700595 """ % {
596 "verdict_var": verdict_var,
597 "lhs": lhs,
598 "rhs": rhs,
599 "operator": operator,
600 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400601
602 comparison_ast = self.str_to_ast(comparison_str)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400603 combined_ast = ast.Module(body=assignments + [comparison_ast])
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400604
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400605 return combined_ast
606 elif k in BINOPS:
607 lhs, rhs = v
608
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400609 top_vvar = verdict_var
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400610
611 self.verdict_next()
612 lvar = self.verdict_variable
613
614 self.verdict_next()
615 rvar = self.verdict_variable
616
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400617 lblock = self.gen_test(fn_template, lhs, lvar)
618 rblock = self.gen_test(fn_template, rhs, rvar)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400619
Zack Williams045b63d2019-01-22 16:30:57 -0700620 invert = ""
621 if k == "&":
622 binop = "and"
623 elif k == "|":
624 binop = "or"
625 elif k == "->":
626 binop = "or"
627 invert = "not"
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400628
629 binop_str = """
630%(verdict_var)s = %(invert)s %(lvar)s %(binop)s %(rvar)s
Zack Williams045b63d2019-01-22 16:30:57 -0700631 """ % {
632 "verdict_var": top_vvar,
633 "invert": invert,
634 "lvar": lvar,
635 "binop": binop,
636 "rvar": rvar,
637 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400638
639 binop_ast = self.str_to_ast(binop_str)
640
641 combined_ast = ast.Module(body=[lblock, rblock, binop_ast])
642 return combined_ast
Zack Williams045b63d2019-01-22 16:30:57 -0700643 elif k == "exists":
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400644 # If the variable starts with a capital letter,
645 # we assume that it is a model. If it starts with
646 # a small letter, we assume it is an enumerable
647 #
648 # We do not support nested exists yet. FIXME.
649
650 var, expr = v
651
652 if var.istitle():
653 f = self.fol_to_python_filter(var, expr, django=True)
654 entry = f.pop()
655
656 python_str = """
Sapan Bhatia9227b4d2017-07-25 23:14:48 -0400657%(verdict_var)s = not not %(model)s.objects.filter(%(query)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700658 """ % {
659 "verdict_var": verdict_var,
660 "model": var,
661 "query": entry,
662 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400663
664 python_ast = ast.parse(python_str)
665 else:
666 f = self.fol_to_python_filter(var, expr, django=False)
667 entry = f.pop()
668
669 python_str = """
670%(verdict_var)s = filter(lambda __elt:%(query)s, %(model)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700671 """ % {
672 "verdict_var": verdict_var,
673 "model": var,
674 "query": entry,
675 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400676
677 python_ast = ast.parse(python_str)
678
679 return python_ast
Zack Williams045b63d2019-01-22 16:30:57 -0700680 elif k == "forall":
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400681 var, expr = v
682
683 if var.istitle():
Zack Williams045b63d2019-01-22 16:30:57 -0700684 f = self.fol_to_python_filter(var, expr, django=True, negate=True)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400685 entry = f.pop()
686
687 self.verdict_next()
688 vvar = self.verdict_variable
689
690 python_str = """
Sapan Bhatia9227b4d2017-07-25 23:14:48 -0400691%(verdict_var)s = not not %(model)s.objects.filter(%(query)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700692 """ % {
693 "verdict_var": vvar,
694 "model": var,
695 "query": entry,
696 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400697
698 python_ast = ast.parse(python_str)
699 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700700 f = self.fol_to_python_filter(var, expr, django=False, negate=True)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400701 entry = f.pop()
702
703 python_str = """
704%(verdict_var)s = next(elt for elt in %(model)s if %(query)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700705 """ % {
706 "verdict_var": vvar,
707 "model": var,
708 "query": entry,
709 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400710
711 python_ast = ast.parse(python_str)
712
713 negate_str = """
714%(verdict_var)s = not %(vvar)s
Zack Williams045b63d2019-01-22 16:30:57 -0700715 """ % {
716 "verdict_var": verdict_var,
717 "vvar": vvar,
718 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400719
720 negate_ast = ast.parse(negate_str)
721
722 return ast.Module(body=[python_ast, negate_ast])
723
Zack Williams045b63d2019-01-22 16:30:57 -0700724
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400725def xproto_fol_to_python_test(policy, fol, model, tag=None):
726 if isinstance(fol, jinja2.Undefined):
Zack Williams045b63d2019-01-22 16:30:57 -0700727 raise Exception("Could not find policy:", policy)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400728
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400729 f2p = FOL2Python()
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400730 fol_reduced = f2p.hoist_outer(fol)
731
Zack Williams045b63d2019-01-22 16:30:57 -0700732 if fol_reduced in ["True", "False"] and fol != fol_reduced:
733 raise TrivialPolicy(
734 "Policy %(name)s trivially reduces to %(reduced)s. If this is what you want, replace its contents with %(reduced)s" % {
735 "name": policy,
736 "reduced": fol_reduced})
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400737
Zack Williams045b63d2019-01-22 16:30:57 -0700738 a = f2p.gen_test_function(fol_reduced, policy, tag="security_check")
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400739
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400740 return astunparse.unparse(a)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400741
Zack Williams045b63d2019-01-22 16:30:57 -0700742
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400743def xproto_fol_to_python_validator(policy, fol, model, message, tag=None):
744 if isinstance(fol, jinja2.Undefined):
Zack Williams045b63d2019-01-22 16:30:57 -0700745 raise Exception("Could not find policy:", policy)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400746
747 f2p = FOL2Python()
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400748 fol_reduced = f2p.hoist_outer(fol)
749
Zack Williams045b63d2019-01-22 16:30:57 -0700750 if fol_reduced in ["True", "False"] and fol != fol_reduced:
751 raise TrivialPolicy(
752 "Policy %(name)s trivially reduces to %(reduced)s. If this is what you want, replace its contents with %(reduced)s" % {
753 "name": policy,
754 "reduced": fol_reduced})
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400755
Zack Williams045b63d2019-01-22 16:30:57 -0700756 a = f2p.gen_validation_function(fol_reduced, policy, message, tag="validator")
757
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400758 return astunparse.unparse(a)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400759
Zack Williams045b63d2019-01-22 16:30:57 -0700760
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400761def main():
762 while True:
Zack Williams045b63d2019-01-22 16:30:57 -0700763 inp = ""
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400764 while True:
765 inp_line = raw_input()
Zack Williams045b63d2019-01-22 16:30:57 -0700766 if inp_line == "EOF":
767 break
768 else:
769 inp += inp_line
770
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400771 fol_lexer = lex.lex(module=FOLLexer())
Zack Williams045b63d2019-01-22 16:30:57 -0700772 fol_parser = yacc.yacc(
773 module=FOLParser(), start="goal", outputdir="/tmp", debug=0
774 )
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400775
776 val = fol_parser.parse(inp, lexer=fol_lexer)
Zack Williams045b63d2019-01-22 16:30:57 -0700777 a = xproto_fol_to_python_test("pol", val, "output", "Test")
778 # f2p = FOL2Python()
779 # a = f2p.hoist_outer(val)
780 print(a)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400781
782
783if __name__ == "__main__":
784 main()