blob: 0997e007699f5f62d80cffe7de204059b12acf4b [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
Zack Williams9a42f872019-02-15 17:56:04 -070015from __future__ import absolute_import, print_function
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040016import astunparse
17import ast
18import random
19import string
Sapan Bhatia5ea307d2017-07-19 00:13:21 -040020import jinja2
Zack Williams9a42f872019-02-15 17:56:04 -070021from plyxproto.parser import lex, yacc
22from plyxproto.logicparser import FOLParser, FOLLexer
23from six.moves import range
24from six.moves import input
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040025
Zack Williams045b63d2019-01-22 16:30:57 -070026BINOPS = ["|", "&", "->"]
27QUANTS = ["exists", "forall"]
28
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040029
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040030class PolicyException(Exception):
31 pass
32
Zack Williams045b63d2019-01-22 16:30:57 -070033
Sapan Bhatiab69f4702017-07-31 16:03:33 -040034class ConstructNotHandled(Exception):
35 pass
36
Zack Williams045b63d2019-01-22 16:30:57 -070037
Sapan Bhatiab69f4702017-07-31 16:03:33 -040038class TrivialPolicy(Exception):
39 pass
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040040
Zack Williams045b63d2019-01-22 16:30:57 -070041
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040042class AutoVariable:
43 def __init__(self, base):
44 self.base = base
45
46 def __iter__(self):
47 self.idx = 0
48 return self
49
Zack Williams9a42f872019-02-15 17:56:04 -070050 def __next__(self):
Zack Williams045b63d2019-01-22 16:30:57 -070051 var = "i%d" % self.idx
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040052 self.idx += 1
53 return var
54
Zack Williams9a42f872019-02-15 17:56:04 -070055 next = __next__ # 2to3
56
Zack Williams045b63d2019-01-22 16:30:57 -070057
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040058def gen_random_string():
Zack Williams045b63d2019-01-22 16:30:57 -070059 return "".join(
60 random.choice(string.ascii_lowercase + string.digits) for _ in range(5)
61 )
62
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040063
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040064class FOL2Python:
65 def __init__(self, context_map=None):
66 # This will produce i0, i1, i2 etc.
Zack Williams045b63d2019-01-22 16:30:57 -070067 self.loopvar = iter(AutoVariable("i"))
68 self.verdictvar = iter(AutoVariable("result"))
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040069
Zack Williams9a42f872019-02-15 17:56:04 -070070 self.loop_variable = next(self.loopvar)
71 self.verdict_variable = next(self.verdictvar)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040072 self.context_map = context_map
73
74 if not self.context_map:
Zack Williams045b63d2019-01-22 16:30:57 -070075 self.context_map = {"user": "self", "obj": "obj"}
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040076
77 def loop_next(self):
Zack Williams9a42f872019-02-15 17:56:04 -070078 self.loop_variable = next(self.loopvar)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040079
80 def verdict_next(self):
Zack Williams9a42f872019-02-15 17:56:04 -070081 self.verdict_variable = next(self.verdictvar)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040082
83 def gen_enumerate(self, fol):
84 pass
85
86 def format_term_for_query(self, model, term, django=False):
Zack Williams045b63d2019-01-22 16:30:57 -070087 if term.startswith(model + "."):
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040088 term = term[len(model) + 1:]
89 if django:
Zack Williams045b63d2019-01-22 16:30:57 -070090 term = term.replace(".", "__")
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040091 else:
Zack Williams045b63d2019-01-22 16:30:57 -070092 term = "__elt" + "." + term
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -040093 return term
94
95 def fol_to_python_filter(self, model, e, django=False, negate=False):
96 try:
97 (k, v), = e.items()
98 except AttributeError:
99 return [self.format_term_for_query(model, e)]
100
101 if django:
102 if negate:
103 # De Morgan's negation
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:
Zack Williams045b63d2019-01-22 16:30:57 -0700108 q_bracket = "Q(%s)"
109 or_expr = "|"
110 and_expr = ","
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400111 else:
112 if negate:
113 # De Morgan's negation
Zack Williams045b63d2019-01-22 16:30:57 -0700114 q_bracket = "not %s"
115 or_expr = " and "
116 and_expr = " or "
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400117 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700118 q_bracket = "%s"
119 or_expr = " or "
120 and_expr = " and "
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400121
Zack Williams045b63d2019-01-22 16:30:57 -0700122 if k in ["=", "in"]:
123 v = [self.format_term_for_query(model, term, django=django) for term in v]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400124 if django:
Zack Williams045b63d2019-01-22 16:30:57 -0700125 operator_map = {"=": " = ", "in": "__in"}
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400126 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700127 operator_map = {"=": " == ", "in": "in"}
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400128 operator = operator_map[k]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400129 return [q_bracket % operator.join(v)]
Zack Williams045b63d2019-01-22 16:30:57 -0700130 elif k == "|":
131 components = [
132 self.fol_to_python_filter(model, x, django=django).pop() for x in v
133 ]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400134 return [or_expr.join(components)]
Zack Williams045b63d2019-01-22 16:30:57 -0700135 elif k == "&":
136 components = [
137 self.fol_to_python_filter(model, x, django=django).pop() for x in v
138 ]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400139 return [and_expr.join(components)]
Zack Williams045b63d2019-01-22 16:30:57 -0700140 elif k == "->":
141 components = [
142 self.fol_to_python_filter(model, x, django=django).pop() for x in v
143 ]
144 return ["~%s | %s" % (components[0], components[1])]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400145
146 """ Convert a single leaf node from a string
147 to an AST"""
Zack Williams045b63d2019-01-22 16:30:57 -0700148
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400149 def str_to_ast(self, s):
150 ast_module = ast.parse(s)
151 return ast_module.body[0]
152
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400153 def reduce_operands(self, operands):
Zack Williams045b63d2019-01-22 16:30:57 -0700154 if operands[0] in ["True", "False"]:
155 return (operands[0], operands[1])
156 elif operands[1] in ["True", "False"]:
157 return (operands[1], operands[0])
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400158 else:
159 return None
160
161 """ Simplify binops with constants """
Zack Williams045b63d2019-01-22 16:30:57 -0700162
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400163 def simplify_binop(self, binop):
Zack Williams045b63d2019-01-22 16:30:57 -0700164 (k, v), = binop.items()
165 if k == "->":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400166 lhs, rhs = v
Zack Williams045b63d2019-01-22 16:30:57 -0700167 if lhs == "True":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400168 return rhs
Zack Williams045b63d2019-01-22 16:30:57 -0700169 elif rhs == "True":
170 return "True"
171 elif lhs == "False":
172 return "True"
173 elif rhs == "False":
174 return {"not": lhs}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400175
176 var_expr = self.reduce_operands(v)
177
Zack Williams045b63d2019-01-22 16:30:57 -0700178 if not var_expr:
179 return binop
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400180 else:
181 constant, var = var_expr
Zack Williams045b63d2019-01-22 16:30:57 -0700182 if k == "|":
183 if constant == "True":
184 return "True"
185 elif constant == "False":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400186 return var
187 else:
188 raise Exception("Internal error - variable read as constant")
Zack Williams045b63d2019-01-22 16:30:57 -0700189 elif k == "&":
190 if constant == "True":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400191 return var
Zack Williams045b63d2019-01-22 16:30:57 -0700192 elif constant == "False":
193 return "False"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400194
195 def is_constant(self, var, fol):
196 try:
197 (k, v), = fol.items()
198 except AttributeError:
Zack Williams045b63d2019-01-22 16:30:57 -0700199 k = "term"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400200 v = fol
Zack Williams045b63d2019-01-22 16:30:57 -0700201
202 if k in ["python", "policy"]:
203 # Treat as a constant and hoist, since it cannot be quantified
204 return True
205 elif k == "term":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400206 return not v.startswith(var)
Zack Williams045b63d2019-01-22 16:30:57 -0700207 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400208 return self.is_constant(var, fol)
Zack Williams045b63d2019-01-22 16:30:57 -0700209 elif k in ["in", "="]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400210 lhs, rhs = v
Zack Williams045b63d2019-01-22 16:30:57 -0700211 return self.is_constant(var, lhs) and self.is_constant(var, rhs)
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400212 elif k in BINOPS:
213 lhs, rhs = v
214 return self.is_constant(lhs, var) and self.is_constant(rhs, var)
215 elif k in QUANTS:
216 is_constant = self.is_constant(var, fol[1])
217 return is_constant
218 else:
219 raise ConstructNotHandled(k)
220
221 def find_constants(self, var, fol, constants):
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400222 try:
223 (k, v), = fol.items()
224 except AttributeError:
Zack Williams045b63d2019-01-22 16:30:57 -0700225 k = "term"
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400226 v = fol
227
Zack Williams045b63d2019-01-22 16:30:57 -0700228 if k in ["python", "policy"]:
229 # Treat as a constant and hoist, since it cannot be quantified
230 if fol not in constants:
231 constants.append(fol)
232 return constants
233 elif k == "term":
234 if not v.startswith(var):
235 constants.append(v)
236 return constants
237 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400238 return self.find_constants(var, v, constants)
Zack Williams045b63d2019-01-22 16:30:57 -0700239 elif k in ["in", "="]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400240 lhs, rhs = v
241 if isinstance(lhs, str) and isinstance(rhs, str):
242 if not lhs.startswith(var) and not rhs.startswith(var):
243 constants.append(fol)
244 return constants
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400245 else:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400246 constants = self.find_constants(var, lhs, constants)
247 return self.find_constants(var, rhs, constants)
248 elif k in BINOPS:
249 lhs, rhs = v
250 constants = self.find_constants(var, lhs, constants)
251 constants = self.find_constants(var, rhs, constants)
252 return constants
253 elif k in QUANTS:
254 is_constant = self.is_constant(var, v[1])
Zack Williams045b63d2019-01-22 16:30:57 -0700255 if is_constant:
256 constants.append(fol)
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400257 return constants
258 else:
259 raise ConstructNotHandled(k)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400260
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400261 """ Hoist constants out of quantifiers. Depth-first. """
Zack Williams045b63d2019-01-22 16:30:57 -0700262
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400263 def hoist_outer(self, fol):
264 try:
265 (k, v), = fol.items()
266 except AttributeError:
Zack Williams045b63d2019-01-22 16:30:57 -0700267 k = "term"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400268 v = fol
269
Zack Williams045b63d2019-01-22 16:30:57 -0700270 if k in ["python", "policy"]:
271 # Tainted, optimization and distribution not possible
272 return fol
273 elif k == "term":
274 return fol
275 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400276 vprime = self.hoist_outer(v)
Zack Williams045b63d2019-01-22 16:30:57 -0700277 return {"not": vprime}
278 elif k in ["in", "="]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400279 lhs, rhs = v
280 rlhs = self.hoist_outer(lhs)
281 rrhs = self.hoist_outer(rhs)
Zack Williams045b63d2019-01-22 16:30:57 -0700282 return {k: [rlhs, rrhs]}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400283 elif k in BINOPS:
284 lhs, rhs = v
285 rlhs = self.hoist_outer(lhs)
286 rrhs = self.hoist_outer(rhs)
287
288 fol_prime = {k: [rlhs, rrhs]}
289 fol_simplified = self.simplify_binop(fol_prime)
290 return fol_simplified
291 elif k in QUANTS:
292 rexpr = self.hoist_outer(v[1])
Zack Williams045b63d2019-01-22 16:30:57 -0700293 return self.hoist_quant(k, [v[0], rexpr])
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400294 else:
295 raise ConstructNotHandled(k)
296
297 def replace_const(self, fol, c, value):
298 if fol == c:
299 return value
300
301 try:
302 (k, v), = fol.items()
303 except AttributeError:
Zack Williams045b63d2019-01-22 16:30:57 -0700304 k = "term"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400305 v = fol
306
Zack Williams045b63d2019-01-22 16:30:57 -0700307 if k == "term":
308 if v == c:
309 return value
310 else:
311 return v
312 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400313 new_expr = self.replace_const(v, c, value)
Zack Williams045b63d2019-01-22 16:30:57 -0700314 if new_expr == "True":
315 return "False"
316 elif new_expr == "False":
317 return "True"
318 else:
319 return {"not": new_expr}
320 elif k in ["in", "="]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400321 lhs, rhs = v
322 rlhs = self.replace_const(lhs, c, value)
323 rrhs = self.replace_const(rhs, c, value)
324
Zack Williams045b63d2019-01-22 16:30:57 -0700325 if rlhs == rrhs:
326 return "True"
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400327 else:
328 return {k: [rlhs, rrhs]}
329 elif k in BINOPS:
330 lhs, rhs = v
331
332 rlhs = self.replace_const(lhs, c, value)
333 rrhs = self.replace_const(rhs, c, value)
Zack Williams045b63d2019-01-22 16:30:57 -0700334
335 return self.simplify_binop({k: [rlhs, rrhs]})
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400336 elif k in QUANTS:
337 var, expr = v
338 new_expr = self.replace_const(expr, c, value)
Zack Williams045b63d2019-01-22 16:30:57 -0700339 if new_expr in ["True", "False"]:
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400340 return new_expr
341 else:
342 return {k: [var, new_expr]}
343 else:
344 raise ConstructNotHandled(k)
345
346 def shannon_expand(self, c, fol):
Zack Williams045b63d2019-01-22 16:30:57 -0700347 lhs = self.replace_const(fol, c, "True")
348 rhs = self.replace_const(fol, c, "False")
349 not_c = {"not": c}
350 rlhs = {"&": [c, lhs]}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400351 rlhs = self.simplify_binop(rlhs)
352
Zack Williams045b63d2019-01-22 16:30:57 -0700353 rrhs = {"&": [not_c, rhs]}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400354 rrhs = self.simplify_binop(rrhs)
355
Zack Williams045b63d2019-01-22 16:30:57 -0700356 combined = {"|": [rlhs, rrhs]}
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400357 return self.simplify_binop(combined)
358
359 def hoist_quant(self, k, expr):
360 var, v = expr
361
362 constants = self.find_constants(var, v, constants=[])
363
364 fol = {k: expr}
365
366 for c in constants:
367 fol = self.shannon_expand(c, fol)
368
369 return fol
370
371 """
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400372 if var:
373 if k == 'term':
374 if not v.startswith(var):
375 return {'hoist': ['const', fol], 'result': 'True'}
376 else:
377 return {'hoist': [], 'result': fol}
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400378 elif k in ['=', 'in']:
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400379 lhs, rhs = v
380 if not lhs.startswith(var) and not rhs.startswith(var):
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400381 return {'hoist': [k, fol], 'result': 'True'} # XXX
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400382 else:
383 return {'hoist': [], 'result': fol}
384 elif k in BINOPS:
385 lhs, rhs = v
386 rlhs = self.hoist_constants(lhs, var)
387 rrhs = self.hoist_constants(rhs, var)
388
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400389 if rlhs['hoist'] and rrhs['hoist'] and rlhs['result']=='True' and llhs['result']=='True':
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400390 return {'hoist': ['=', fol], 'result': 'True'}
391 elif rlhs['hoist']:
392 return {'hoist': [k, lhs], 'result': rhs}
393 elif rrhs['hoist']:
394 return {'hoist': [k, rhs], 'result': lhs}
395 else:
396 return {'hoist': [], 'result': fol}
397
398 elif k in QUANTS:
399 var2, expr = v
400 result = self.hoist_constants(expr, var2)
401 if result['hoist']:
402 if result['result'] == 'True':
403 return {'hoist': ['const'], 'result': result['hoist'][1]}
404 elif result['hoist'][0] in BINOPS:
Zack Williams9a42f872019-02-15 17:56:04 -0700405 return {'hoist': ['const'], 'result': {result['hoist'][0]:
406 [result['hoist'][1], {k: [var2, result['result']]}]}}
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400407 else:
408 return {'hoist': ['const'], 'result': {k: [var2, result['result']]}}
409 else:
410 result = self.hoist_constants(expr, var)
411 if result['result'] == 'True':
412 return {'hoist': ['&', fol], 'result': 'True'}
413 else:
414 return {'hoist': [], 'result': fol}
415 else:
416 return {'hoist': [], 'result': fol}
417 else:
418 if k in BINOPS:
419 lhs, rhs = v
420 rlhs = self.hoist_constants(lhs)
421 rrhs = self.hoist_constants(rhs)
422 return {k: [rlhs, rrhs]}
423 elif k in QUANTS:
424 var, expr = v
425 result = self.hoist_constants(expr, var)
426 if result['hoist']:
427 if result['result'] == 'True':
428 return result['hoist'][1]
429 elif result['hoist'][0] in BINOPS:
430 return {result['hoist'][0]: [result['hoist'][1], {k: [var, result['result']]}]}
431 else:
432 return {k: [var, result['result']]}
433 else:
434 return fol
435 else:
436 return fol
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400437 """
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400438
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400439 def gen_validation_function(self, fol, policy_name, message, tag):
440 if not tag:
441 tag = gen_random_string()
442
Zack Williams045b63d2019-01-22 16:30:57 -0700443 policy_function_name_template = "policy_%s_" + "%(random_string)s" % {
444 "random_string": tag
445 }
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400446 policy_function_name = policy_function_name_template % policy_name
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400447 self.verdict_next()
448 function_str = """
449def %(fn_name)s(obj, ctx):
Scott Bakera8b6d692018-04-17 10:21:32 -0700450 if not %(vvar)s: raise XOSValidationError("%(message)s".format(obj=obj, ctx=ctx))
Zack Williams045b63d2019-01-22 16:30:57 -0700451 """ % {
452 "fn_name": policy_function_name,
453 "vvar": self.verdict_variable,
454 "message": message,
455 }
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400456
457 function_ast = self.str_to_ast(function_str)
Zack Williams045b63d2019-01-22 16:30:57 -0700458 policy_code = self.gen_test(
459 policy_function_name_template, fol, self.verdict_variable
460 )
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400461
462 function_ast.body = [policy_code] + function_ast.body
463
464 return function_ast
465
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400466 def gen_test_function(self, fol, policy_name, tag):
467 if not tag:
468 tag = gen_random_string()
469
Zack Williams045b63d2019-01-22 16:30:57 -0700470 policy_function_name_template = "%s_" + "%(random_string)s" % {
471 "random_string": tag
472 }
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400473 policy_function_name = policy_function_name_template % policy_name
474
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400475 self.verdict_next()
476 function_str = """
477def %(fn_name)s(obj, ctx):
478 return %(vvar)s
Zack Williams045b63d2019-01-22 16:30:57 -0700479 """ % {
480 "fn_name": policy_function_name,
481 "vvar": self.verdict_variable,
482 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400483
484 function_ast = self.str_to_ast(function_str)
Zack Williams045b63d2019-01-22 16:30:57 -0700485 policy_code = self.gen_test(
486 policy_function_name_template, fol, self.verdict_variable
487 )
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400488
489 function_ast.body = [policy_code] + function_ast.body
490
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400491 return function_ast
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400492
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400493 def gen_test(self, fn_template, fol, verdict_var, bindings=None):
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400494 if isinstance(fol, str):
Zack Williams045b63d2019-01-22 16:30:57 -0700495 return self.str_to_ast(
496 "%(verdict_var)s = %(constant)s"
497 % {"verdict_var": verdict_var, "constant": fol}
498 )
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400499
500 (k, v), = fol.items()
501
Zack Williams045b63d2019-01-22 16:30:57 -0700502 if k == "policy":
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400503 policy_name, object_name = v
504
505 policy_fn = fn_template % policy_name
506 call_str = """
Sapan Bhatiac6543dd2017-12-07 11:40:36 -0500507if obj.%(object_name)s:
508 %(verdict_var)s = %(policy_fn)s(obj.%(object_name)s, ctx)
509else:
510 # Everybody has access to null objects
511 %(verdict_var)s = True
Zack Williams045b63d2019-01-22 16:30:57 -0700512 """ % {
513 "verdict_var": verdict_var,
514 "policy_fn": policy_fn,
515 "object_name": object_name,
516 }
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400517
518 call_ast = self.str_to_ast(call_str)
519 return call_ast
Zack Williams045b63d2019-01-22 16:30:57 -0700520 if k == "python":
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400521 try:
522 expr_ast = self.str_to_ast(v)
523 except SyntaxError:
Zack Williams045b63d2019-01-22 16:30:57 -0700524 raise PolicyException("Syntax error in %s" % v)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400525
526 if not isinstance(expr_ast, ast.Expr):
Zack Williams045b63d2019-01-22 16:30:57 -0700527 raise PolicyException("%s is not an expression" % expr_ast)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400528
529 assignment_str = """
530%(verdict_var)s = (%(escape_expr)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700531 """ % {
532 "verdict_var": verdict_var,
533 "escape_expr": v,
534 }
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400535
536 assignment_ast = self.str_to_ast(assignment_str)
537 return assignment_ast
Zack Williams045b63d2019-01-22 16:30:57 -0700538 elif k == "not":
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400539 top_vvar = verdict_var
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400540 self.verdict_next()
541 sub_vvar = self.verdict_variable
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400542 block = self.gen_test(fn_template, v, sub_vvar)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400543 assignment_str = """
544%(verdict_var)s = not (%(subvar)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700545 """ % {
546 "verdict_var": top_vvar,
547 "subvar": sub_vvar,
548 }
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400549
550 assignment_ast = self.str_to_ast(assignment_str)
551
552 return ast.Module(body=[block, assignment_ast])
Zack Williams045b63d2019-01-22 16:30:57 -0700553 elif k in ["=", "in"]:
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400554 # This is the simplest case, we don't recurse further
555 # To use terms that are not simple variables, use
556 # the Python escape, e.g. {{ slice.creator is not None }}
557 lhs, rhs = v
558
559 assignments = []
560
561 try:
562 for t in lhs, rhs:
Zack Williams045b63d2019-01-22 16:30:57 -0700563 py_expr = t["python"]
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400564
565 self.verdict_next()
566 vv = self.verdict_variable
567
568 try:
569 expr_ast = self.str_to_ast(py_expr)
570 except SyntaxError:
Zack Williams045b63d2019-01-22 16:30:57 -0700571 raise PolicyException("Syntax error in %s" % v)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400572
573 if not isinstance(expr_ast, ast.Expr):
Zack Williams045b63d2019-01-22 16:30:57 -0700574 raise PolicyException("%s is not an expression" % expr_ast)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400575
576 assignment_str = """
577%(verdict_var)s = (%(escape_expr)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700578 """ % {
579 "verdict_var": vv,
580 "escape_expr": py_expr,
581 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400582
583 if t == lhs:
584 lhs = vv
585 else:
586 rhs = vv
587
588 assignment_ast = self.str_to_ast(assignment_str)
589 assignments.append(assignment_ast)
590 except TypeError:
591 pass
592
Zack Williams045b63d2019-01-22 16:30:57 -0700593 if k == "=":
594 operator = "=="
595 elif k == "in":
596 operator = "in"
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400597
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400598 comparison_str = """
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400599%(verdict_var)s = (%(lhs)s %(operator)s %(rhs)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700600 """ % {
601 "verdict_var": verdict_var,
602 "lhs": lhs,
603 "rhs": rhs,
604 "operator": operator,
605 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400606
607 comparison_ast = self.str_to_ast(comparison_str)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400608 combined_ast = ast.Module(body=assignments + [comparison_ast])
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400609
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400610 return combined_ast
611 elif k in BINOPS:
612 lhs, rhs = v
613
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400614 top_vvar = verdict_var
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400615
616 self.verdict_next()
617 lvar = self.verdict_variable
618
619 self.verdict_next()
620 rvar = self.verdict_variable
621
Sapan Bhatiad3fcb662017-07-25 21:13:48 -0400622 lblock = self.gen_test(fn_template, lhs, lvar)
623 rblock = self.gen_test(fn_template, rhs, rvar)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400624
Zack Williams045b63d2019-01-22 16:30:57 -0700625 invert = ""
626 if k == "&":
627 binop = "and"
628 elif k == "|":
629 binop = "or"
630 elif k == "->":
631 binop = "or"
632 invert = "not"
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400633
634 binop_str = """
635%(verdict_var)s = %(invert)s %(lvar)s %(binop)s %(rvar)s
Zack Williams045b63d2019-01-22 16:30:57 -0700636 """ % {
637 "verdict_var": top_vvar,
638 "invert": invert,
639 "lvar": lvar,
640 "binop": binop,
641 "rvar": rvar,
642 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400643
644 binop_ast = self.str_to_ast(binop_str)
645
646 combined_ast = ast.Module(body=[lblock, rblock, binop_ast])
647 return combined_ast
Zack Williams045b63d2019-01-22 16:30:57 -0700648 elif k == "exists":
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400649 # If the variable starts with a capital letter,
650 # we assume that it is a model. If it starts with
651 # a small letter, we assume it is an enumerable
652 #
653 # We do not support nested exists yet. FIXME.
654
655 var, expr = v
656
657 if var.istitle():
658 f = self.fol_to_python_filter(var, expr, django=True)
659 entry = f.pop()
660
661 python_str = """
Sapan Bhatia9227b4d2017-07-25 23:14:48 -0400662%(verdict_var)s = not not %(model)s.objects.filter(%(query)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700663 """ % {
664 "verdict_var": verdict_var,
665 "model": var,
666 "query": entry,
667 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400668
669 python_ast = ast.parse(python_str)
670 else:
671 f = self.fol_to_python_filter(var, expr, django=False)
672 entry = f.pop()
673
674 python_str = """
675%(verdict_var)s = filter(lambda __elt:%(query)s, %(model)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700676 """ % {
677 "verdict_var": verdict_var,
678 "model": var,
679 "query": entry,
680 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400681
682 python_ast = ast.parse(python_str)
683
684 return python_ast
Zack Williams045b63d2019-01-22 16:30:57 -0700685 elif k == "forall":
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400686 var, expr = v
687
688 if var.istitle():
Zack Williams045b63d2019-01-22 16:30:57 -0700689 f = self.fol_to_python_filter(var, expr, django=True, negate=True)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400690 entry = f.pop()
691
692 self.verdict_next()
693 vvar = self.verdict_variable
694
695 python_str = """
Sapan Bhatia9227b4d2017-07-25 23:14:48 -0400696%(verdict_var)s = not not %(model)s.objects.filter(%(query)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700697 """ % {
698 "verdict_var": vvar,
699 "model": var,
700 "query": entry,
701 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400702
703 python_ast = ast.parse(python_str)
704 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700705 f = self.fol_to_python_filter(var, expr, django=False, negate=True)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400706 entry = f.pop()
707
708 python_str = """
709%(verdict_var)s = next(elt for elt in %(model)s if %(query)s)
Zack Williams045b63d2019-01-22 16:30:57 -0700710 """ % {
711 "verdict_var": vvar,
712 "model": var,
713 "query": entry,
714 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400715
716 python_ast = ast.parse(python_str)
717
718 negate_str = """
719%(verdict_var)s = not %(vvar)s
Zack Williams045b63d2019-01-22 16:30:57 -0700720 """ % {
721 "verdict_var": verdict_var,
722 "vvar": vvar,
723 }
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400724
725 negate_ast = ast.parse(negate_str)
726
727 return ast.Module(body=[python_ast, negate_ast])
728
Zack Williams045b63d2019-01-22 16:30:57 -0700729
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400730def xproto_fol_to_python_test(policy, fol, model, tag=None):
731 if isinstance(fol, jinja2.Undefined):
Zack Williams045b63d2019-01-22 16:30:57 -0700732 raise Exception("Could not find policy:", policy)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400733
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400734 f2p = FOL2Python()
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400735 fol_reduced = f2p.hoist_outer(fol)
736
Zack Williams045b63d2019-01-22 16:30:57 -0700737 if fol_reduced in ["True", "False"] and fol != fol_reduced:
738 raise TrivialPolicy(
Zack Williams9a42f872019-02-15 17:56:04 -0700739 "Policy %(name)s trivially reduces to %(reduced)s."
740 "If this is what you want, replace its contents with %(reduced)s"
741 % {"name": policy, "reduced": fol_reduced}
742 )
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400743
Zack Williams045b63d2019-01-22 16:30:57 -0700744 a = f2p.gen_test_function(fol_reduced, policy, tag="security_check")
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400745
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400746 return astunparse.unparse(a)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400747
Zack Williams045b63d2019-01-22 16:30:57 -0700748
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400749def xproto_fol_to_python_validator(policy, fol, model, message, tag=None):
750 if isinstance(fol, jinja2.Undefined):
Zack Williams045b63d2019-01-22 16:30:57 -0700751 raise Exception("Could not find policy:", policy)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400752
753 f2p = FOL2Python()
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400754 fol_reduced = f2p.hoist_outer(fol)
755
Zack Williams045b63d2019-01-22 16:30:57 -0700756 if fol_reduced in ["True", "False"] and fol != fol_reduced:
757 raise TrivialPolicy(
Zack Williams9a42f872019-02-15 17:56:04 -0700758 "Policy %(name)s trivially reduces to %(reduced)s."
759 "If this is what you want, replace its contents with %(reduced)s"
760 % {"name": policy, "reduced": fol_reduced}
761 )
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400762
Zack Williams045b63d2019-01-22 16:30:57 -0700763 a = f2p.gen_validation_function(fol_reduced, policy, message, tag="validator")
764
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400765 return astunparse.unparse(a)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400766
Zack Williams045b63d2019-01-22 16:30:57 -0700767
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400768def main():
769 while True:
Zack Williams045b63d2019-01-22 16:30:57 -0700770 inp = ""
Sapan Bhatiab69f4702017-07-31 16:03:33 -0400771 while True:
Zack Williams9a42f872019-02-15 17:56:04 -0700772 inp_line = input()
Zack Williams045b63d2019-01-22 16:30:57 -0700773 if inp_line == "EOF":
774 break
775 else:
776 inp += inp_line
777
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400778 fol_lexer = lex.lex(module=FOLLexer())
Zack Williams045b63d2019-01-22 16:30:57 -0700779 fol_parser = yacc.yacc(
780 module=FOLParser(), start="goal", outputdir="/tmp", debug=0
781 )
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400782
783 val = fol_parser.parse(inp, lexer=fol_lexer)
Zack Williams045b63d2019-01-22 16:30:57 -0700784 a = xproto_fol_to_python_test("pol", val, "output", "Test")
785 # f2p = FOL2Python()
786 # a = f2p.hoist_outer(val)
787 print(a)
Sapan Bhatia3e3c1cd2017-07-15 01:35:44 -0400788
789
790if __name__ == "__main__":
791 main()