equation 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f4f1469e22a3d098dfcb0c2ca974a2d081c0c7be90afddb20fea36f65bdc0804
4
+ data.tar.gz: 5925da1a74a22bfe8e1cc1b7f4dd43a61cf02b05a61ca2a15821f184ff45e5a7
5
+ SHA512:
6
+ metadata.gz: 5ef71408034ed55d48efadd5b99c6e3c41ae49680b673f658b5fa0689731834a0260e4810c3a189f670b97c6451845b3c082ef9ad82ff65c813de5b13a6dee27
7
+ data.tar.gz: 595552e82f8f4412b0a379025883669b0f05e1199948de2ed1221c14092a17cd2b296f396ad2e3e0091e1095307b392904866457222d21895c0c7b66978dda87
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Equation [WIP]
2
+
3
+ A rules engine for your Ruby app! Use a constrained and relatively safe language to express logic for your Ruby app, without having to write Ruby. This allows you to use text (e.g. in a configuration file or database) to store logic in a way that can be updated independently from your code faster than it takes for a deploy without opening extra security vulnerabilities.
4
+
5
+ Use cases include:
6
+
7
+ * writing rules to describe HTTP traffic that then gets dropped, like a WAF
8
+ * writing policies to express authorization logic
9
+ * etc
10
+
11
+ Modeled loosely after [Symfony Expression Language](https://symfony.com/doc/current/components/expression_language.html).
12
+
13
+ ## Example
14
+
15
+ In this example, we'll use a rule to determine whether a request should be dropped or not. While the rule here is hardcoded into the program, it could just as easily be pulled from a database, some redis cache, etc instead. Rules can also be cached, saving you an extra parsing step.
16
+
17
+ ```ruby
18
+ require 'equation'
19
+
20
+ # set up the execution environment and give it access to the rails request object
21
+ engine = EquationEngine.new(default: {
22
+ age: 12,
23
+ username: "OMAR",
24
+ request: request
25
+ })
26
+
27
+ suspicious_request = engine.eval(rule: '$request.path == "/api/login" && $request.remote_ip == "1.2.3.4" && $username == "OMAR"')
28
+ if suspicious_request
29
+ # log some things, notify some people
30
+ end
31
+ ```
data/lib/equation.rb ADDED
@@ -0,0 +1,68 @@
1
+ require 'treetop'
2
+ require_relative 'equation_node_classes'
3
+ require_relative 'equation_grammar'
4
+
5
+ class Context
6
+ def initialize(default: {}, methods: {})
7
+ @symbol_table = default
8
+ @methods = methods
9
+ end
10
+
11
+ def set(identifier:, value:)
12
+ @symbol_table[identifier.to_sym] = value
13
+ end
14
+
15
+ def get(identifier:, path: {})
16
+ assert_defined!(identifier: identifier)
17
+ @symbol_table[identifier.to_sym]
18
+ root = @symbol_table[identifier.to_sym]
19
+ child = root
20
+ path.each{|segment|
21
+ segment_name = segment.elements[1].text_value.to_sym
22
+ if child.respond_to?(segment_name)
23
+ child = child.send(segment_name)
24
+ elsif child.include?(segment_name)
25
+ child = child[segment_name]
26
+ else
27
+ raise "no"
28
+ end
29
+ }
30
+
31
+ child
32
+ end
33
+
34
+ def call(method:, args:)
35
+ assert_method_exists!(method: method)
36
+ @methods[method.to_sym].call(*args)
37
+ end
38
+
39
+ private
40
+ def assert_defined!(identifier:)
41
+ raise "Undefined variable: #{identifier}" unless @symbol_table.has_key?(identifier.to_sym)
42
+ end
43
+
44
+ def assert_method_exists!(method:)
45
+ raise "Undefined method: #{method}" unless @methods.has_key?(method.to_sym)
46
+ end
47
+ end
48
+
49
+ class EquationEngine
50
+ def initialize(default: {}, methods: {})
51
+ @parser = EquationParser.new
52
+ @context = Context.new(default: default, methods: methods)
53
+ end
54
+
55
+ def parse(rule:)
56
+ parsed_rule = @parser.parse(rule)
57
+ raise "Parse Error: #{@parser.failure_reason}" unless parsed_rule
58
+
59
+ parsed_rule
60
+ end
61
+
62
+ def eval(rule:)
63
+ parsed_rule = @parser.parse(rule)
64
+ raise "Parse Error: #{rule}" unless parsed_rule
65
+
66
+ parsed_rule.value(ctx: @context)
67
+ end
68
+ end