okta-expression-parser 0.2.2__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: okta-expression-parser
3
- Version: 0.2.2
3
+ Version: 0.4.0
4
4
  Summary: Returns a boolean by passing Okta profile data to an Okta Expression using Okta Expression Language
5
5
  Author: Mathew Moon
6
6
  Author-email: me@mathewmoon.net
@@ -10,9 +10,9 @@ class ExpressionLexer(Lexer):
10
10
  # reflags = re.IGNORECASE
11
11
  # Set of token names.
12
12
  tokens = {
13
+ NAME,
13
14
  STRING,
14
15
  INT,
15
- NAME,
16
16
  NE,
17
17
  GTE,
18
18
  LTE,
@@ -24,8 +24,14 @@ class ExpressionLexer(Lexer):
24
24
  NOT,
25
25
  NULL,
26
26
  BOOL,
27
+ COLON,
28
+ QUESTION_MARK,
27
29
  MEMBEROF,
28
30
  MEMBEROFANY,
31
+ MEMBEROFNAME,
32
+ MEMBEROFGROUPSTARTSWITH,
33
+ MEMBEROFGROUPCONTAINS,
34
+ MEMBEROFGROUPNAMEREGEX,
29
35
  USER,
30
36
  CLASS,
31
37
  }
@@ -46,14 +52,21 @@ class ExpressionLexer(Lexer):
46
52
  OR = r"or|OR|Or"
47
53
  EQ = r"(==|eq|EQ|Eq)"
48
54
  BOOL = r"(true|false|True|False|TRUE|FALSE)"
55
+ MEMBEROFGROUPCONTAINS = "isMemberOfGroupNameContains"
56
+ MEMBEROFGROUPNAMEREGEX = "isMemberOfGroupNameRegex"
57
+ MEMBEROFGROUPSTARTSWITH = "isMemberOfGroupNameStartsWith"
49
58
  MEMBEROFANY = "isMemberOfAnyGroup"
59
+ MEMBEROFNAME = "isMemberOfGroupName"
50
60
  MEMBEROF = "isMemberOfGroup"
51
61
  USER = r"user\b"
62
+ COLON = r"\:"
63
+ QUESTION_MARK = r"\?"
52
64
 
53
65
  NULL = r"null|NULL|Null"
54
66
  CLASS = r"String|Arrays|Convert|Iso3166Convert|Groups"
55
67
  INT = r"\d+"
56
- STRING = r""""([^"\\]*(\\.[^"\\]*)*)"|\'([^\'\\]*(\\.[^\'\\]*)*)\'|''|\"\""""
68
+ # STRING = r""""([^"\\]*(\\.[^"\\]*)*)"|\'([^\'\\]*(\\.[^\'\\]*)*)\'|''|\"\""""
69
+ STRING = r'''"(?:\\.|[^"\\])*"'''
57
70
  NAME = r"[a-zA-Z_][a-zA-Z0-9\-_]*"
58
71
 
59
72
  # Line number tracking
@@ -1,6 +1,7 @@
1
1
  # pyright: reportUndefinedVariable=false
2
2
  import logging
3
3
  from os import environ
4
+ from re import match
4
5
  from sys import stdout
5
6
  from typing import Any, Dict, List, Sequence
6
7
  from types import ModuleType
@@ -14,9 +15,8 @@ from .lexer import ExpressionLexer
14
15
 
15
16
 
16
17
  class ExpressionParser(sly.Parser):
17
- if debug_file := environ.get("OKTA_EXPRESSION_PARSER_LOG_FILE"):
18
- #: Writes grammar rules to a file for debugging
19
- debugfile: str = environ.get(debug_file)
18
+ #: Writes grammar rules to a file for debugging
19
+ debugfile = environ.get("OKTA_EXPRESSION_PARSER_LOG_FILE")
20
20
 
21
21
  #: Gets the token list from the lexer (required)
22
22
  tokens: Sequence = ExpressionLexer.tokens
@@ -50,6 +50,7 @@ class ExpressionParser(sly.Parser):
50
50
  ):
51
51
  self._expression_classes.Groups.group_data = group_data
52
52
 
53
+ #: A dict, with Group ID's as the keys and group data as the values. Used for operations that require more context than just the group ID.
53
54
  self.group_data = group_data
54
55
 
55
56
  self.__group_ids: str = group_ids
@@ -64,6 +65,12 @@ class ExpressionParser(sly.Parser):
64
65
  def parse(self, expression: str):
65
66
  return super().parse(ExpressionLexer().tokenize(expression))
66
67
 
68
+ @_("operand") # noqa: 821
69
+ @_("condition") # noqa: 821
70
+ def result(self, p: YaccProduction) -> bool: # noqa: 811
71
+ res = p.condition if hasattr(p, "condition") else p.operand
72
+ return res
73
+
67
74
  @_("operand EQ operand") # noqa: 821
68
75
  def condition(self, p: YaccProduction) -> bool:
69
76
  operand0 = p.operand0
@@ -107,17 +114,39 @@ class ExpressionParser(sly.Parser):
107
114
  operand1 = p.operand1
108
115
  return type(operand0) == type(operand1) and operand0 > operand1
109
116
 
117
+ @_('"(" condition ")"') # noqa: 821
118
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
119
+ return p.condition
120
+
121
+ @_('"(" operand ")"') # noqa: 821
122
+ def operand(self, p: YaccProduction) -> bool: # noqa: 811
123
+ return p.operand
124
+
110
125
  @_("condition AND condition") # noqa: 821
111
126
  def condition(self, p: YaccProduction) -> bool: # noqa: 811
112
- condition0 = p.condition0
113
- condition1 = p.condition1
114
- return condition0 and condition1
127
+ return p.condition0 and p.condition1
128
+
129
+ @_("operand AND operand") # noqa: 821
130
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
131
+ return p.operand0 and p.operand1
132
+
133
+ @_("operand AND condition") # noqa: 821
134
+ @_("condition AND operand") # noqa: 821
135
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
136
+ return p.condition and p.operand
115
137
 
116
138
  @_("condition OR condition") # noqa: 821
117
139
  def condition(self, p: YaccProduction) -> bool: # noqa: 811
118
- condition0 = p.condition0
119
- condition1 = p.condition1
120
- return condition0 or condition1
140
+ return p.condition0 or p.condition1
141
+
142
+ @_("operand OR operand") # noqa: 821
143
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
144
+ return p.operand0 or p.operand1
145
+
146
+ @_("operand OR condition") # noqa: 821
147
+ @_("condition OR operand") # noqa: 821
148
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
149
+ return p.condition or p.operand
121
150
 
122
151
  @_("NOT condition") # noqa: 821
123
152
  def condition(self, p: YaccProduction) -> bool: # noqa: 811
@@ -128,22 +157,11 @@ class ExpressionParser(sly.Parser):
128
157
  def condition(self, p: YaccProduction) -> bool: # noqa: 811
129
158
  return not p.operand
130
159
 
131
- @_('"(" condition ")"') # noqa: 821
132
- def condition(self, p: YaccProduction) -> bool: # noqa: 811
133
- condition = p.condition
134
- return condition
135
-
136
160
  @_('operand "," operand') # noqa: 821
137
161
  def operand(self, p: YaccProduction) -> List[Any]: # noqa: 811
138
162
  operand0 = p.operand0
139
163
  operand1 = p.operand1
140
164
 
141
- # if not isinstance(operand0, tuple):
142
- # return (operand0, operand1)
143
- #
144
- # else:
145
- # return operand0 + (operand1,)
146
-
147
165
  if isinstance(operand0, tuple) and isinstance(operand1, tuple):
148
166
  res = operand0 + operand1
149
167
  elif isinstance(operand0, tuple) and not isinstance(operand1, tuple):
@@ -309,6 +327,57 @@ class ExpressionParser(sly.Parser):
309
327
 
310
328
  return p.operand in self.__group_ids
311
329
 
330
+ @_('MEMBEROFNAME "(" operand ")"')
331
+ def condition(self, p: YaccProduction):
332
+ """Tests if a user is a member of a group with a specific name"""
333
+
334
+ matches = [
335
+ v for k, v in self.group_data.items() if v["profile"]["name"] == p.operand
336
+ ]
337
+
338
+ return bool(matches)
339
+
340
+ @_('MEMBEROFGROUPSTARTSWITH "(" operand ")"')
341
+ def condition(self, p: YaccProduction):
342
+ """Tests if a user is a member of a group with a specific name"""
343
+
344
+ matches = [
345
+ v
346
+ for k, v in self.group_data.items()
347
+ if v["profile"]["name"].startswith(p.operand)
348
+ ]
349
+
350
+ return bool(matches)
351
+
352
+ @_('MEMBEROFGROUPCONTAINS "(" operand ")"')
353
+ def condition(self, p: YaccProduction):
354
+ """Tests if a user is a member of a group with a specific name"""
355
+
356
+ matches = [
357
+ v for k, v in self.group_data.items() if p.operand in v["profile"]["name"]
358
+ ]
359
+
360
+ return bool(matches)
361
+
362
+ @_('MEMBEROFGROUPNAMEREGEX "(" operand ")"')
363
+ def condition(self, p: YaccProduction):
364
+ """Tests if a user is a member of a group with a specific name"""
365
+
366
+ for _, group in self.group_data.items():
367
+ if match(p.operand, group["profile"]["name"]):
368
+ return True
369
+
370
+ return False
371
+
372
+ @_("operand QUESTION_MARK operand COLON operand") # noqa: 821
373
+ @_("condition QUESTION_MARK operand COLON operand") # noqa: 821
374
+ def operand(self, p: YaccProduction) -> Any: # noqa: 811
375
+ condition = p.condition if hasattr(p, "condition") else p.operand0
376
+ if_true = p.operand0 if hasattr(p, "condition") else p.operand1
377
+ if_false = p.operand1 if hasattr(p, "condition") else p.operand2
378
+
379
+ return if_true if condition else if_false
380
+
312
381
  @_("condition error") # noqa: 821
313
382
  def operand(self, x): # noqa: 811
314
383
  raise SyntaxError(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "okta-expression-parser"
3
- version = "0.2.2"
3
+ version = "0.4.0"
4
4
  description = "Returns a boolean by passing Okta profile data to an Okta Expression using Okta Expression Language"
5
5
  authors = ["Mathew Moon <me@mathewmoon.net>"]
6
6
  readme = "README.md"