okta-expression-parser 0.2.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.
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.1
2
+ Name: okta-expression-parser
3
+ Version: 0.2.0
4
+ Summary: Returns a boolean by passing Okta profile data to an Okta Expression using Okta Expression Language
5
+ Author: Mathew Moon
6
+ Author-email: me@mathewmoon.net
7
+ Requires-Python: >=3.11,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Requires-Dist: sly (>=0.5,<0.6)
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Okta expression parser
15
+
@@ -0,0 +1 @@
1
+ # Okta expression parser
@@ -0,0 +1,5 @@
1
+ from .arrays import Arrays
2
+ from .string import String
3
+ from .convert import Convert
4
+ from .iso3166convert import Iso3166Convert
5
+ from .groups import Groups
@@ -0,0 +1,57 @@
1
+ from typing import Any, List
2
+
3
+
4
+ class _ArrayType(list):
5
+ pass
6
+
7
+
8
+ class Arrays:
9
+ @classmethod
10
+ def contains(cls, array: _ArrayType, val: str) -> bool:
11
+ """Tests if a value exists in an expression's array."""
12
+ return val in array
13
+
14
+ @classmethod
15
+ def add(cls, array: _ArrayType, val: Any) -> _ArrayType:
16
+ """Appends an element to a list and returns the list."""
17
+ return _ArrayType(array + [val])
18
+
19
+ @classmethod
20
+ def remove(cls, array: _ArrayType, val: Any) -> _ArrayType:
21
+ """Removes all occurances of a value from a list."""
22
+ return _ArrayType([x for x in array if x != val])
23
+
24
+ @classmethod
25
+ def clear(cls, _: _ArrayType) -> _ArrayType:
26
+ """Returns and empty list."""
27
+ return _ArrayType([])
28
+
29
+ @classmethod
30
+ def get(cls, array: _ArrayType, index: int) -> Any:
31
+ """Returns element at `index`. If `index` does not exist then returns None."""
32
+ try:
33
+ return array[index]
34
+ except IndexError:
35
+ return None
36
+
37
+ @classmethod
38
+ def flatten(cls, *args: _ArrayType) -> _ArrayType:
39
+ """Returns a flattened list from all args."""
40
+ return _ArrayType(
41
+ [item for row in args for item in (row if isinstance(row, list) else [row])]
42
+ )
43
+
44
+ @classmethod
45
+ def size(cls, array: _ArrayType) -> int:
46
+ """Returns the number of elements in array."""
47
+ return len(array)
48
+
49
+ @classmethod
50
+ def isEmpty(cls, array: _ArrayType) -> bool:
51
+ """Returns if an array is empty or not."""
52
+ return not array
53
+
54
+ @classmethod
55
+ def toCsvString(cls, array: _ArrayType) -> str:
56
+ """Returns a comma delineated string from array elements."""
57
+ return ",".join(array)
@@ -0,0 +1,15 @@
1
+ class Convert:
2
+ @classmethod
3
+ def toInt(val: str | float) -> int:
4
+ """Casts a string to an int."""
5
+ try:
6
+ return int(val)
7
+ except ValueError:
8
+ return 0
9
+
10
+ @classmethod
11
+ def toNum(val: str) -> float:
12
+ try:
13
+ return float(val)
14
+ except ValueError:
15
+ return 0
@@ -0,0 +1,25 @@
1
+ from .arrays import _ArrayType
2
+
3
+
4
+ class Groups:
5
+ group_data = {}
6
+
7
+ @classmethod
8
+ def getFilteredGroups(
9
+ cls, allow_list: _ArrayType, group_expression: str, limit: int = 5
10
+ ) -> _ArrayType:
11
+ """
12
+ Returns all groups that are in `allow_list`, which is a list of ID's AND the user is a member of. The return value is
13
+ the key specified by `group_expression`. For example, if `group_expression` is "user.name" then
14
+ a list of group names will be returned.
15
+ """
16
+ group_key = group_expression.split(".")[-1]
17
+ res = [cls.group_data.get(x, {}).get(group_key) for x in allow_list]
18
+
19
+ if not res:
20
+ return _ArrayType(res)
21
+
22
+ if len(res) > limit:
23
+ res = res[0:limit]
24
+
25
+ return _ArrayType([x for x in res if x is not None])
@@ -0,0 +1,470 @@
1
+ from typing import Dict
2
+
3
+
4
+ class Iso3166Convert:
5
+ @classmethod
6
+ def __get_identifier_type(cls, identifier: str) -> str:
7
+ if len(identifier) > 3:
8
+ return "name"
9
+ if len(identifier) == 2:
10
+ return "alpha2"
11
+ try:
12
+ int(identifier)
13
+ return "numeric"
14
+ except ValueError:
15
+ return "alpha3"
16
+
17
+ @classmethod
18
+ def __get_country(cls, identifier: str) -> Dict[str, str]:
19
+ """Returns a country's dict, looking it up by an identifier and what type of identifier it is."""
20
+ if not isinstance(identifier, str):
21
+ return None
22
+
23
+ id_type = cls.__get_identifier_type(identifier)
24
+ country = [x for x in codes if x[id_type].lower() == identifier.lower()]
25
+
26
+ if country:
27
+ return country[0]
28
+
29
+ @classmethod
30
+ def toAlpha2(cls, identifier: str) -> str:
31
+ if res := cls.__get_country(identifier):
32
+ return res["alpha2"]
33
+
34
+ @classmethod
35
+ def toAlpha3(cls, identifier: str) -> str:
36
+ if res := cls.__get_country(identifier):
37
+ return res["alpha3"]
38
+
39
+ @classmethod
40
+ def toNumeric(cls, identifier: str) -> str:
41
+ if res := cls.__get_country(identifier):
42
+ return res["numeric"]
43
+
44
+ @classmethod
45
+ def toName(cls, identifier: str) -> str:
46
+ if res := cls.__get_country(identifier):
47
+ return res["name"]
48
+
49
+
50
+ codes = [
51
+ {"name": "Andorra", "alpha2": "AD", "alpha3": "AND", "numeric": "020"},
52
+ {"name": "United Arab Emirates", "alpha2": "AE", "alpha3": "ARE", "numeric": "784"},
53
+ {"name": "Afghanistan", "alpha2": "AF", "alpha3": "AFG", "numeric": "004"},
54
+ {"name": "Antigua and Barbuda", "alpha2": "AG", "alpha3": "ATG", "numeric": "028"},
55
+ {"name": "Anguilla", "alpha2": "AI", "alpha3": "AIA", "numeric": "660"},
56
+ {"name": "Albania", "alpha2": "AL", "alpha3": "ALB", "numeric": "008"},
57
+ {"name": "Armenia", "alpha2": "AM", "alpha3": "ARM", "numeric": "051"},
58
+ {"name": "Angola", "alpha2": "AO", "alpha3": "AGO", "numeric": "024"},
59
+ {"name": "Antarctica", "alpha2": "AQ", "alpha3": "ATA", "numeric": "010"},
60
+ {"name": "Argentina", "alpha2": "AR", "alpha3": "ARG", "numeric": "032"},
61
+ {"name": "American Samoa", "alpha2": "AS", "alpha3": "ASM", "numeric": "016"},
62
+ {"name": "Austria", "alpha2": "AT", "alpha3": "AUT", "numeric": "040"},
63
+ {"name": "Australia", "alpha2": "AU", "alpha3": "AUS", "numeric": "036"},
64
+ {"name": "Aruba", "alpha2": "AW", "alpha3": "ABW", "numeric": "533"},
65
+ {"name": "Åland Islands", "alpha2": "AX", "alpha3": "ALA", "numeric": "248"},
66
+ {"name": "Azerbaijan", "alpha2": "AZ", "alpha3": "AZE", "numeric": "031"},
67
+ {
68
+ "name": "Bosnia and Herzegovina",
69
+ "alpha2": "BA",
70
+ "alpha3": "BIH",
71
+ "numeric": "070",
72
+ },
73
+ {"name": "Barbados", "alpha2": "BB", "alpha3": "BRB", "numeric": "052"},
74
+ {"name": "Bangladesh", "alpha2": "BD", "alpha3": "BGD", "numeric": "050"},
75
+ {"name": "Belgium", "alpha2": "BE", "alpha3": "BEL", "numeric": "056"},
76
+ {"name": "Burkina Faso", "alpha2": "BF", "alpha3": "BFA", "numeric": "854"},
77
+ {"name": "Bulgaria", "alpha2": "BG", "alpha3": "BGR", "numeric": "100"},
78
+ {"name": "Bahrain", "alpha2": "BH", "alpha3": "BHR", "numeric": "048"},
79
+ {"name": "Burundi", "alpha2": "BI", "alpha3": "BDI", "numeric": "108"},
80
+ {"name": "Benin", "alpha2": "BJ", "alpha3": "BEN", "numeric": "204"},
81
+ {"name": "Saint Barthélemy", "alpha2": "BL", "alpha3": "BLM", "numeric": "652"},
82
+ {"name": "Bermuda", "alpha2": "BM", "alpha3": "BMU", "numeric": "060"},
83
+ {"name": "Brunei Darussalam", "alpha2": "BN", "alpha3": "BRN", "numeric": "096"},
84
+ {
85
+ "name": "Bolivia (Plurinational State of)",
86
+ "alpha2": "BO",
87
+ "alpha3": "BOL",
88
+ "numeric": "068",
89
+ },
90
+ {
91
+ "name": "Bonaire, Sint Eustatius and Saba",
92
+ "alpha2": "BQ",
93
+ "alpha3": "BES",
94
+ "numeric": "535",
95
+ },
96
+ {"name": "Brazil", "alpha2": "BR", "alpha3": "BRA", "numeric": "076"},
97
+ {"name": "Bahamas", "alpha2": "BS", "alpha3": "BHS", "numeric": "044"},
98
+ {"name": "Bhutan", "alpha2": "BT", "alpha3": "BTN", "numeric": "064"},
99
+ {"name": "Bouvet Island", "alpha2": "BV", "alpha3": "BVT", "numeric": "074"},
100
+ {"name": "Botswana", "alpha2": "BW", "alpha3": "BWA", "numeric": "072"},
101
+ {"name": "Belarus", "alpha2": "BY", "alpha3": "BLR", "numeric": "112"},
102
+ {"name": "Belize", "alpha2": "BZ", "alpha3": "BLZ", "numeric": "084"},
103
+ {"name": "Canada", "alpha2": "CA", "alpha3": "CAN", "numeric": "124"},
104
+ {
105
+ "name": "Cocos (Keeling) Islands",
106
+ "alpha2": "CC",
107
+ "alpha3": "CCK",
108
+ "numeric": "166",
109
+ },
110
+ {
111
+ "name": "Congo, Democratic Republic of the",
112
+ "alpha2": "CD",
113
+ "alpha3": "COD",
114
+ "numeric": "180",
115
+ },
116
+ {
117
+ "name": "Central African Republic",
118
+ "alpha2": "CF",
119
+ "alpha3": "CAF",
120
+ "numeric": "140",
121
+ },
122
+ {"name": "Congo", "alpha2": "CG", "alpha3": "COG", "numeric": "178"},
123
+ {"name": "Switzerland", "alpha2": "CH", "alpha3": "CHE", "numeric": "756"},
124
+ {"name": "Côte d'Ivoire", "alpha2": "CI", "alpha3": "CIV", "numeric": "384"},
125
+ {"name": "Cook Islands", "alpha2": "CK", "alpha3": "COK", "numeric": "184"},
126
+ {"name": "Chile", "alpha2": "CL", "alpha3": "CHL", "numeric": "152"},
127
+ {"name": "Cameroon", "alpha2": "CM", "alpha3": "CMR", "numeric": "120"},
128
+ {"name": "China", "alpha2": "CN", "alpha3": "CHN", "numeric": "156"},
129
+ {"name": "Colombia", "alpha2": "CO", "alpha3": "COL", "numeric": "170"},
130
+ {"name": "Costa Rica", "alpha2": "CR", "alpha3": "CRI", "numeric": "188"},
131
+ {"name": "Cuba", "alpha2": "CU", "alpha3": "CUB", "numeric": "192"},
132
+ {"name": "Cabo Verde", "alpha2": "CV", "alpha3": "CPV", "numeric": "132"},
133
+ {"name": "Curaçao", "alpha2": "CW", "alpha3": "CUW", "numeric": "531"},
134
+ {"name": "Christmas Island", "alpha2": "CX", "alpha3": "CXR", "numeric": "162"},
135
+ {"name": "Cyprus", "alpha2": "CY", "alpha3": "CYP", "numeric": "196"},
136
+ {"name": "Czechia", "alpha2": "CZ", "alpha3": "CZE", "numeric": "203"},
137
+ {"name": "Germany", "alpha2": "DE", "alpha3": "DEU", "numeric": "276"},
138
+ {"name": "Djibouti", "alpha2": "DJ", "alpha3": "DJI", "numeric": "262"},
139
+ {"name": "Denmark", "alpha2": "DK", "alpha3": "DNK", "numeric": "208"},
140
+ {"name": "Dominica", "alpha2": "DM", "alpha3": "DMA", "numeric": "212"},
141
+ {"name": "Dominican Republic", "alpha2": "DO", "alpha3": "DOM", "numeric": "214"},
142
+ {"name": "Algeria", "alpha2": "DZ", "alpha3": "DZA", "numeric": "012"},
143
+ {"name": "Ecuador", "alpha2": "EC", "alpha3": "ECU", "numeric": "218"},
144
+ {"name": "Estonia", "alpha2": "EE", "alpha3": "EST", "numeric": "233"},
145
+ {"name": "Egypt", "alpha2": "EG", "alpha3": "EGY", "numeric": "818"},
146
+ {"name": "Western Sahara", "alpha2": "EH", "alpha3": "ESH", "numeric": "732"},
147
+ {"name": "Eritrea", "alpha2": "ER", "alpha3": "ERI", "numeric": "232"},
148
+ {"name": "Spain", "alpha2": "ES", "alpha3": "ESP", "numeric": "724"},
149
+ {"name": "Ethiopia", "alpha2": "ET", "alpha3": "ETH", "numeric": "231"},
150
+ {"name": "Finland", "alpha2": "FI", "alpha3": "FIN", "numeric": "246"},
151
+ {"name": "Fiji", "alpha2": "FJ", "alpha3": "FJI", "numeric": "242"},
152
+ {
153
+ "name": "Falkland Islands (Malvinas)",
154
+ "alpha2": "FK",
155
+ "alpha3": "FLK",
156
+ "numeric": "238",
157
+ },
158
+ {
159
+ "name": "Micronesia (Federated States of)",
160
+ "alpha2": "FM",
161
+ "alpha3": "FSM",
162
+ "numeric": "583",
163
+ },
164
+ {"name": "Faroe Islands", "alpha2": "FO", "alpha3": "FRO", "numeric": "234"},
165
+ {"name": "France", "alpha2": "FR", "alpha3": "FRA", "numeric": "250"},
166
+ {"name": "Gabon", "alpha2": "GA", "alpha3": "GAB", "numeric": "266"},
167
+ {
168
+ "name": "United Kingdom of Great Britain and Northern Ireland",
169
+ "alpha2": "GB",
170
+ "alpha3": "GBR",
171
+ "numeric": "826",
172
+ },
173
+ {"name": "Grenada", "alpha2": "GD", "alpha3": "GRD", "numeric": "308"},
174
+ {"name": "Georgia", "alpha2": "GE", "alpha3": "GEO", "numeric": "268"},
175
+ {"name": "French Guiana", "alpha2": "GF", "alpha3": "GUF", "numeric": "254"},
176
+ {"name": "Guernsey", "alpha2": "GG", "alpha3": "GGY", "numeric": "831"},
177
+ {"name": "Ghana", "alpha2": "GH", "alpha3": "GHA", "numeric": "288"},
178
+ {"name": "Gibraltar", "alpha2": "GI", "alpha3": "GIB", "numeric": "292"},
179
+ {"name": "Greenland", "alpha2": "GL", "alpha3": "GRL", "numeric": "304"},
180
+ {"name": "Gambia", "alpha2": "GM", "alpha3": "GMB", "numeric": "270"},
181
+ {"name": "Guinea", "alpha2": "GN", "alpha3": "GIN", "numeric": "324"},
182
+ {"name": "Guadeloupe", "alpha2": "GP", "alpha3": "GLP", "numeric": "312"},
183
+ {"name": "Equatorial Guinea", "alpha2": "GQ", "alpha3": "GNQ", "numeric": "226"},
184
+ {"name": "Greece", "alpha2": "GR", "alpha3": "GRC", "numeric": "300"},
185
+ {
186
+ "name": "South Georgia and the South Sandwich Islands",
187
+ "alpha2": "GS",
188
+ "alpha3": "SGS",
189
+ "numeric": "239",
190
+ },
191
+ {"name": "Guatemala", "alpha2": "GT", "alpha3": "GTM", "numeric": "320"},
192
+ {"name": "Guam", "alpha2": "GU", "alpha3": "GUM", "numeric": "316"},
193
+ {"name": "Guinea-Bissau", "alpha2": "GW", "alpha3": "GNB", "numeric": "624"},
194
+ {"name": "Guyana", "alpha2": "GY", "alpha3": "GUY", "numeric": "328"},
195
+ {"name": "Hong Kong", "alpha2": "HK", "alpha3": "HKG", "numeric": "344"},
196
+ {
197
+ "name": "Heard Island and McDonald Islands",
198
+ "alpha2": "HM",
199
+ "alpha3": "HMD",
200
+ "numeric": "334",
201
+ },
202
+ {"name": "Honduras", "alpha2": "HN", "alpha3": "HND", "numeric": "340"},
203
+ {"name": "Croatia", "alpha2": "HR", "alpha3": "HRV", "numeric": "191"},
204
+ {"name": "Haiti", "alpha2": "HT", "alpha3": "HTI", "numeric": "332"},
205
+ {"name": "Hungary", "alpha2": "HU", "alpha3": "HUN", "numeric": "348"},
206
+ {"name": "Indonesia", "alpha2": "ID", "alpha3": "IDN", "numeric": "360"},
207
+ {"name": "Ireland", "alpha2": "IE", "alpha3": "IRL", "numeric": "372"},
208
+ {"name": "Israel", "alpha2": "IL", "alpha3": "ISR", "numeric": "376"},
209
+ {"name": "Isle of Man", "alpha2": "IM", "alpha3": "IMN", "numeric": "833"},
210
+ {"name": "India", "alpha2": "IN", "alpha3": "IND", "numeric": "356"},
211
+ {
212
+ "name": "British Indian Ocean Territory",
213
+ "alpha2": "IO",
214
+ "alpha3": "IOT",
215
+ "numeric": "086",
216
+ },
217
+ {"name": "Iraq", "alpha2": "IQ", "alpha3": "IRQ", "numeric": "368"},
218
+ {
219
+ "name": "Iran (Islamic Republic of)",
220
+ "alpha2": "IR",
221
+ "alpha3": "IRN",
222
+ "numeric": "364",
223
+ },
224
+ {"name": "Iceland", "alpha2": "IS", "alpha3": "ISL", "numeric": "352"},
225
+ {"name": "Italy", "alpha2": "IT", "alpha3": "ITA", "numeric": "380"},
226
+ {"name": "Jersey", "alpha2": "JE", "alpha3": "JEY", "numeric": "832"},
227
+ {"name": "Jamaica", "alpha2": "JM", "alpha3": "JAM", "numeric": "388"},
228
+ {"name": "Jordan", "alpha2": "JO", "alpha3": "JOR", "numeric": "400"},
229
+ {"name": "Japan", "alpha2": "JP", "alpha3": "JPN", "numeric": "392"},
230
+ {"name": "Kenya", "alpha2": "KE", "alpha3": "KEN", "numeric": "404"},
231
+ {"name": "Kyrgyzstan", "alpha2": "KG", "alpha3": "KGZ", "numeric": "417"},
232
+ {"name": "Cambodia", "alpha2": "KH", "alpha3": "KHM", "numeric": "116"},
233
+ {"name": "Kiribati", "alpha2": "KI", "alpha3": "KIR", "numeric": "296"},
234
+ {"name": "Comoros", "alpha2": "KM", "alpha3": "COM", "numeric": "174"},
235
+ {
236
+ "name": "Saint Kitts and Nevis",
237
+ "alpha2": "KN",
238
+ "alpha3": "KNA",
239
+ "numeric": "659",
240
+ },
241
+ {
242
+ "name": "Korea (Democratic People's Republic of)",
243
+ "alpha2": "KP",
244
+ "alpha3": "PRK",
245
+ "numeric": "408",
246
+ },
247
+ {"name": "Korea, Republic of", "alpha2": "KR", "alpha3": "KOR", "numeric": "410"},
248
+ {"name": "Kuwait", "alpha2": "KW", "alpha3": "KWT", "numeric": "414"},
249
+ {"name": "Cayman Islands", "alpha2": "KY", "alpha3": "CYM", "numeric": "136"},
250
+ {"name": "Kazakhstan", "alpha2": "KZ", "alpha3": "KAZ", "numeric": "398"},
251
+ {
252
+ "name": "Lao People's Democratic Republic",
253
+ "alpha2": "LA",
254
+ "alpha3": "LAO",
255
+ "numeric": "418",
256
+ },
257
+ {"name": "Lebanon", "alpha2": "LB", "alpha3": "LBN", "numeric": "422"},
258
+ {"name": "Saint Lucia", "alpha2": "LC", "alpha3": "LCA", "numeric": "662"},
259
+ {"name": "Liechtenstein", "alpha2": "LI", "alpha3": "LIE", "numeric": "438"},
260
+ {"name": "Sri Lanka", "alpha2": "LK", "alpha3": "LKA", "numeric": "144"},
261
+ {"name": "Liberia", "alpha2": "LR", "alpha3": "LBR", "numeric": "430"},
262
+ {"name": "Lesotho", "alpha2": "LS", "alpha3": "LSO", "numeric": "426"},
263
+ {"name": "Lithuania", "alpha2": "LT", "alpha3": "LTU", "numeric": "440"},
264
+ {"name": "Luxembourg", "alpha2": "LU", "alpha3": "LUX", "numeric": "442"},
265
+ {"name": "Latvia", "alpha2": "LV", "alpha3": "LVA", "numeric": "428"},
266
+ {"name": "Libya", "alpha2": "LY", "alpha3": "LBY", "numeric": "434"},
267
+ {"name": "Morocco", "alpha2": "MA", "alpha3": "MAR", "numeric": "504"},
268
+ {"name": "Monaco", "alpha2": "MC", "alpha3": "MCO", "numeric": "492"},
269
+ {"name": "Moldova, Republic of", "alpha2": "MD", "alpha3": "MDA", "numeric": "498"},
270
+ {"name": "Montenegro", "alpha2": "ME", "alpha3": "MNE", "numeric": "499"},
271
+ {
272
+ "name": "Saint Martin (French part)",
273
+ "alpha2": "MF",
274
+ "alpha3": "MAF",
275
+ "numeric": "663",
276
+ },
277
+ {"name": "Madagascar", "alpha2": "MG", "alpha3": "MDG", "numeric": "450"},
278
+ {"name": "Marshall Islands", "alpha2": "MH", "alpha3": "MHL", "numeric": "584"},
279
+ {"name": "North Macedonia", "alpha2": "MK", "alpha3": "MKD", "numeric": "807"},
280
+ {"name": "Mali", "alpha2": "ML", "alpha3": "MLI", "numeric": "466"},
281
+ {"name": "Myanmar", "alpha2": "MM", "alpha3": "MMR", "numeric": "104"},
282
+ {"name": "Mongolia", "alpha2": "MN", "alpha3": "MNG", "numeric": "496"},
283
+ {"name": "Macao", "alpha2": "MO", "alpha3": "MAC", "numeric": "446"},
284
+ {
285
+ "name": "Northern Mariana Islands",
286
+ "alpha2": "MP",
287
+ "alpha3": "MNP",
288
+ "numeric": "580",
289
+ },
290
+ {"name": "Martinique", "alpha2": "MQ", "alpha3": "MTQ", "numeric": "474"},
291
+ {"name": "Mauritania", "alpha2": "MR", "alpha3": "MRT", "numeric": "478"},
292
+ {"name": "Montserrat", "alpha2": "MS", "alpha3": "MSR", "numeric": "500"},
293
+ {"name": "Malta", "alpha2": "MT", "alpha3": "MLT", "numeric": "470"},
294
+ {"name": "Mauritius", "alpha2": "MU", "alpha3": "MUS", "numeric": "480"},
295
+ {"name": "Maldives", "alpha2": "MV", "alpha3": "MDV", "numeric": "462"},
296
+ {"name": "Malawi", "alpha2": "MW", "alpha3": "MWI", "numeric": "454"},
297
+ {"name": "Mexico", "alpha2": "MX", "alpha3": "MEX", "numeric": "484"},
298
+ {"name": "Malaysia", "alpha2": "MY", "alpha3": "MYS", "numeric": "458"},
299
+ {"name": "Mozambique", "alpha2": "MZ", "alpha3": "MOZ", "numeric": "508"},
300
+ {"name": "Namibia", "alpha2": "NA", "alpha3": "NAM", "numeric": "516"},
301
+ {"name": "New Caledonia", "alpha2": "NC", "alpha3": "NCL", "numeric": "540"},
302
+ {"name": "Niger", "alpha2": "NE", "alpha3": "NER", "numeric": "562"},
303
+ {"name": "Norfolk Island", "alpha2": "NF", "alpha3": "NFK", "numeric": "574"},
304
+ {"name": "Nigeria", "alpha2": "NG", "alpha3": "NGA", "numeric": "566"},
305
+ {"name": "Nicaragua", "alpha2": "NI", "alpha3": "NIC", "numeric": "558"},
306
+ {
307
+ "name": "Netherlands, Kingdom of the",
308
+ "alpha2": "NL",
309
+ "alpha3": "NLD",
310
+ "numeric": "528",
311
+ },
312
+ {"name": "Norway", "alpha2": "NO", "alpha3": "NOR", "numeric": "578"},
313
+ {"name": "Nepal", "alpha2": "NP", "alpha3": "NPL", "numeric": "524"},
314
+ {"name": "Nauru", "alpha2": "NR", "alpha3": "NRU", "numeric": "520"},
315
+ {"name": "Niue", "alpha2": "NU", "alpha3": "NIU", "numeric": "570"},
316
+ {"name": "New Zealand", "alpha2": "NZ", "alpha3": "NZL", "numeric": "554"},
317
+ {"name": "Oman", "alpha2": "OM", "alpha3": "OMN", "numeric": "512"},
318
+ {"name": "Panama", "alpha2": "PA", "alpha3": "PAN", "numeric": "591"},
319
+ {"name": "Peru", "alpha2": "PE", "alpha3": "PER", "numeric": "604"},
320
+ {"name": "French Polynesia", "alpha2": "PF", "alpha3": "PYF", "numeric": "258"},
321
+ {"name": "Papua New Guinea", "alpha2": "PG", "alpha3": "PNG", "numeric": "598"},
322
+ {"name": "Philippines", "alpha2": "PH", "alpha3": "PHL", "numeric": "608"},
323
+ {"name": "Pakistan", "alpha2": "PK", "alpha3": "PAK", "numeric": "586"},
324
+ {"name": "Poland", "alpha2": "PL", "alpha3": "POL", "numeric": "616"},
325
+ {
326
+ "name": "Saint Pierre and Miquelon",
327
+ "alpha2": "PM",
328
+ "alpha3": "SPM",
329
+ "numeric": "666",
330
+ },
331
+ {"name": "Pitcairn", "alpha2": "PN", "alpha3": "PCN", "numeric": "612"},
332
+ {"name": "Puerto Rico", "alpha2": "PR", "alpha3": "PRI", "numeric": "630"},
333
+ {"name": "Palestine, State of", "alpha2": "PS", "alpha3": "PSE", "numeric": "275"},
334
+ {"name": "Portugal", "alpha2": "PT", "alpha3": "PRT", "numeric": "620"},
335
+ {"name": "Palau", "alpha2": "PW", "alpha3": "PLW", "numeric": "585"},
336
+ {"name": "Paraguay", "alpha2": "PY", "alpha3": "PRY", "numeric": "600"},
337
+ {"name": "Qatar", "alpha2": "QA", "alpha3": "QAT", "numeric": "634"},
338
+ {"name": "Réunion", "alpha2": "RE", "alpha3": "REU", "numeric": "638"},
339
+ {"name": "Romania", "alpha2": "RO", "alpha3": "ROU", "numeric": "642"},
340
+ {"name": "Serbia", "alpha2": "RS", "alpha3": "SRB", "numeric": "688"},
341
+ {"name": "Russian Federation", "alpha2": "RU", "alpha3": "RUS", "numeric": "643"},
342
+ {"name": "Rwanda", "alpha2": "RW", "alpha3": "RWA", "numeric": "646"},
343
+ {"name": "Saudi Arabia", "alpha2": "SA", "alpha3": "SAU", "numeric": "682"},
344
+ {"name": "Solomon Islands", "alpha2": "SB", "alpha3": "SLB", "numeric": "090"},
345
+ {"name": "Seychelles", "alpha2": "SC", "alpha3": "SYC", "numeric": "690"},
346
+ {"name": "Sudan", "alpha2": "SD", "alpha3": "SDN", "numeric": "729"},
347
+ {"name": "Sweden", "alpha2": "SE", "alpha3": "SWE", "numeric": "752"},
348
+ {"name": "Singapore", "alpha2": "SG", "alpha3": "SGP", "numeric": "702"},
349
+ {
350
+ "name": "Saint Helena, Ascension and Tristan da Cunha",
351
+ "alpha2": "SH",
352
+ "alpha3": "SHN",
353
+ "numeric": "654",
354
+ },
355
+ {"name": "Slovenia", "alpha2": "SI", "alpha3": "SVN", "numeric": "705"},
356
+ {
357
+ "name": "Svalbard and Jan Mayen",
358
+ "alpha2": "SJ",
359
+ "alpha3": "SJM",
360
+ "numeric": "744",
361
+ },
362
+ {"name": "Slovakia", "alpha2": "SK", "alpha3": "SVK", "numeric": "703"},
363
+ {"name": "Sierra Leone", "alpha2": "SL", "alpha3": "SLE", "numeric": "694"},
364
+ {"name": "San Marino", "alpha2": "SM", "alpha3": "SMR", "numeric": "674"},
365
+ {"name": "Senegal", "alpha2": "SN", "alpha3": "SEN", "numeric": "686"},
366
+ {"name": "Somalia", "alpha2": "SO", "alpha3": "SOM", "numeric": "706"},
367
+ {"name": "Suriname", "alpha2": "SR", "alpha3": "SUR", "numeric": "740"},
368
+ {"name": "South Sudan", "alpha2": "SS", "alpha3": "SSD", "numeric": "728"},
369
+ {
370
+ "name": "Sao Tome and Principe",
371
+ "alpha2": "ST",
372
+ "alpha3": "STP",
373
+ "numeric": "678",
374
+ },
375
+ {"name": "El Salvador", "alpha2": "SV", "alpha3": "SLV", "numeric": "222"},
376
+ {
377
+ "name": "Sint Maarten (Dutch part)",
378
+ "alpha2": "SX",
379
+ "alpha3": "SXM",
380
+ "numeric": "534",
381
+ },
382
+ {"name": "Syrian Arab Republic", "alpha2": "SY", "alpha3": "SYR", "numeric": "760"},
383
+ {"name": "Eswatini", "alpha2": "SZ", "alpha3": "SWZ", "numeric": "748"},
384
+ {
385
+ "name": "Turks and Caicos Islands",
386
+ "alpha2": "TC",
387
+ "alpha3": "TCA",
388
+ "numeric": "796",
389
+ },
390
+ {"name": "Chad", "alpha2": "TD", "alpha3": "TCD", "numeric": "148"},
391
+ {
392
+ "name": "French Southern Territories",
393
+ "alpha2": "TF",
394
+ "alpha3": "ATF",
395
+ "numeric": "260",
396
+ },
397
+ {"name": "Togo", "alpha2": "TG", "alpha3": "TGO", "numeric": "768"},
398
+ {"name": "Thailand", "alpha2": "TH", "alpha3": "THA", "numeric": "764"},
399
+ {"name": "Tajikistan", "alpha2": "TJ", "alpha3": "TJK", "numeric": "762"},
400
+ {"name": "Tokelau", "alpha2": "TK", "alpha3": "TKL", "numeric": "772"},
401
+ {"name": "Timor-Leste", "alpha2": "TL", "alpha3": "TLS", "numeric": "626"},
402
+ {"name": "Turkmenistan", "alpha2": "TM", "alpha3": "TKM", "numeric": "795"},
403
+ {"name": "Tunisia", "alpha2": "TN", "alpha3": "TUN", "numeric": "788"},
404
+ {"name": "Tonga", "alpha2": "TO", "alpha3": "TON", "numeric": "776"},
405
+ {"name": "Türkiye", "alpha2": "TR", "alpha3": "TUR", "numeric": "792"},
406
+ {"name": "Trinidad and Tobago", "alpha2": "TT", "alpha3": "TTO", "numeric": "780"},
407
+ {"name": "Tuvalu", "alpha2": "TV", "alpha3": "TUV", "numeric": "798"},
408
+ {
409
+ "name": "Taiwan, Province of China",
410
+ "alpha2": "TW",
411
+ "alpha3": "TWN",
412
+ "numeric": "158",
413
+ },
414
+ {
415
+ "name": "Tanzania, United Republic of",
416
+ "alpha2": "TZ",
417
+ "alpha3": "TZA",
418
+ "numeric": "834",
419
+ },
420
+ {"name": "Ukraine", "alpha2": "UA", "alpha3": "UKR", "numeric": "804"},
421
+ {"name": "Uganda", "alpha2": "UG", "alpha3": "UGA", "numeric": "800"},
422
+ {
423
+ "name": "United States Minor Outlying Islands",
424
+ "alpha2": "UM",
425
+ "alpha3": "UMI",
426
+ "numeric": "581",
427
+ },
428
+ {
429
+ "name": "United States of America",
430
+ "alpha2": "US",
431
+ "alpha3": "USA",
432
+ "numeric": "840",
433
+ },
434
+ {"name": "Uruguay", "alpha2": "UY", "alpha3": "URY", "numeric": "858"},
435
+ {"name": "Uzbekistan", "alpha2": "UZ", "alpha3": "UZB", "numeric": "860"},
436
+ {"name": "Holy See", "alpha2": "VA", "alpha3": "VAT", "numeric": "336"},
437
+ {
438
+ "name": "Saint Vincent and the Grenadines",
439
+ "alpha2": "VC",
440
+ "alpha3": "VCT",
441
+ "numeric": "670",
442
+ },
443
+ {
444
+ "name": "Venezuela (Bolivarian Republic of)",
445
+ "alpha2": "VE",
446
+ "alpha3": "VEN",
447
+ "numeric": "862",
448
+ },
449
+ {
450
+ "name": "Virgin Islands (British)",
451
+ "alpha2": "VG",
452
+ "alpha3": "VGB",
453
+ "numeric": "092",
454
+ },
455
+ {
456
+ "name": "Virgin Islands (U.S.)",
457
+ "alpha2": "VI",
458
+ "alpha3": "VIR",
459
+ "numeric": "850",
460
+ },
461
+ {"name": "Viet Nam", "alpha2": "VN", "alpha3": "VNM", "numeric": "704"},
462
+ {"name": "Vanuatu", "alpha2": "VU", "alpha3": "VUT", "numeric": "548"},
463
+ {"name": "Wallis and Futuna", "alpha2": "WF", "alpha3": "WLF", "numeric": "876"},
464
+ {"name": "Samoa", "alpha2": "WS", "alpha3": "WSM", "numeric": "882"},
465
+ {"name": "Yemen", "alpha2": "YE", "alpha3": "YEM", "numeric": "887"},
466
+ {"name": "Mayotte", "alpha2": "YT", "alpha3": "MYT", "numeric": "175"},
467
+ {"name": "South Africa", "alpha2": "ZA", "alpha3": "ZAF", "numeric": "710"},
468
+ {"name": "Zambia", "alpha2": "ZM", "alpha3": "ZMB", "numeric": "894"},
469
+ {"name": "Zimbabwe", "alpha2": "ZW", "alpha3": "ZWE", "numeric": "716"},
470
+ ]
@@ -0,0 +1,111 @@
1
+ from typing import Tuple
2
+
3
+
4
+ class String:
5
+ @classmethod
6
+ def stringContains(cls, str_to_test: str, val: str):
7
+ """Tests if a string contains another string"""
8
+ res = (
9
+ isinstance(str_to_test, str) and isinstance(val, str) and val in str_to_test
10
+ )
11
+ return res
12
+
13
+ @classmethod
14
+ def startsWith(self, str_to_test: str, val: str):
15
+ """Tests if a string starts with another string"""
16
+ return (
17
+ isinstance(str_to_test, str)
18
+ and isinstance(val, str)
19
+ and str_to_test.startswith(val)
20
+ )
21
+
22
+ @classmethod
23
+ def toLowerCase(self, val: str):
24
+ """Casts to lower case"""
25
+ if isinstance(val, str):
26
+ return val.lower()
27
+
28
+ return val
29
+
30
+ @classmethod
31
+ def toUpperCase(self, val: str):
32
+ """Casts to upper case"""
33
+ if isinstance(val, str):
34
+ return val.upper()
35
+ return val
36
+
37
+ @classmethod
38
+ def append(self, val1: str, val2: str):
39
+ """Concatenates two strings"""
40
+ return f"{val1}${val2}"
41
+
42
+ @classmethod
43
+ def join(self, separator: str, *vals: Tuple[str]):
44
+ """Returns a joined string using separator"""
45
+ return separator.join(list(vals))
46
+
47
+ @classmethod
48
+ def removeSpaces(self, val: str):
49
+ """Removes spaces from a string"""
50
+ return str(val).replace(" ", "")
51
+
52
+ @classmethod
53
+ def replace(self, val: str, match: str, replacement: str):
54
+ """Replace all occurances of match with replacement in val"""
55
+ return str(val).replace(match, replacement)
56
+
57
+ @classmethod
58
+ def replaceFirst(self, val: str, match: str, replacement: str):
59
+ """Replace first occurance of match with replacement in val"""
60
+ return str(val).replace(match, replacement, 1)
61
+
62
+ @classmethod
63
+ def startsWith(self, val: str, test: str):
64
+ """Returns whether val starts with test"""
65
+ return str(val).startswith(test)
66
+
67
+ @classmethod
68
+ def contains(self, val: str, test: str):
69
+ """Return whether val contains test"""
70
+ return str(test) in str(val)
71
+
72
+ @classmethod
73
+ def stringSwitch(self, val: str, default: str, *kv_pairs: Tuple[str]):
74
+ """Set defaultss"""
75
+ if len(kv_pairs) % 2 != 0:
76
+ return False
77
+
78
+ kv_pairs = list(kv_pairs)
79
+
80
+ while kv_pairs:
81
+ k = kv_pairs.pop(0)
82
+ v = kv_pairs.pop(0)
83
+
84
+ if k in val:
85
+ return v
86
+
87
+ return default
88
+
89
+ @classmethod
90
+ def substring(val: str, start: int, end: int):
91
+ """Return a substring of val starting at index start"""
92
+ return val[int(start) : int(end)]
93
+
94
+ @classmethod
95
+ def substringAfter(val: str, search: str):
96
+ try:
97
+ start = str(val).index(str(search))
98
+ except ValueError:
99
+ return val
100
+
101
+ return val[start:]
102
+
103
+ @classmethod
104
+ def substringBefore(val: str, search: str):
105
+ """Return substring of val starting after the index of search string"""
106
+ try:
107
+ end = str(val).index(str(search))
108
+ except ValueError:
109
+ return val
110
+
111
+ return val[0:end]
@@ -0,0 +1,68 @@
1
+ # pyright: reportUndefinedVariable=false
2
+ from sly import Lexer
3
+
4
+
5
+ class SyntaxError(Exception):
6
+ pass
7
+
8
+
9
+ class ExpressionLexer(Lexer):
10
+ # reflags = re.IGNORECASE
11
+ # Set of token names.
12
+ tokens = {
13
+ STRING,
14
+ INT,
15
+ NAME,
16
+ NE,
17
+ GTE,
18
+ LTE,
19
+ GT,
20
+ LT,
21
+ EQ,
22
+ AND,
23
+ OR,
24
+ NOT,
25
+ NULL,
26
+ BOOL,
27
+ MEMBEROF,
28
+ MEMBEROFANY,
29
+ USER,
30
+ CLASS,
31
+ }
32
+
33
+ # Set of literal characters
34
+ literals = {"(", ")", ",", ".", "{", "}"}
35
+
36
+ # String containing ignored characters
37
+ ignore = " \t"
38
+
39
+ AND = r"and|And|AND"
40
+ NE = r"(!=|ne|NE|Ne)"
41
+ NOT = r"!|not|Not|NOT"
42
+ GTE = ">="
43
+ GT = ">"
44
+ LT = "<"
45
+ LTE = "<="
46
+ OR = r"or|OR|Or"
47
+ EQ = r"(==|eq|EQ|Eq)"
48
+ BOOL = r"(true|false|True|False|TRUE|FALSE)"
49
+ MEMBEROFANY = "isMemberOfAnyGroup"
50
+ MEMBEROF = "isMemberOfGroup"
51
+ USER = r"user\b"
52
+
53
+ NULL = r"null|NULL|Null"
54
+ CLASS = r"String|Arrays|Convert|Iso3166Convert|Groups"
55
+ INT = r"\d+"
56
+ STRING = r""""([^"\\]*(\\.[^"\\]*)*)"|\'([^\'\\]*(\\.[^\'\\]*)*)\'|''|\"\""""
57
+ NAME = r"[a-zA-Z_][a-zA-Z0-9\-_]*"
58
+
59
+ # Line number tracking
60
+ @_(r"\n+")
61
+ def ignore_newline(self, t):
62
+ self.lineno += t.value.count("\n")
63
+
64
+ def error(self, t):
65
+ msg = (
66
+ f"Bad character '{t.value[0]}' at line {self.lineno} character {self.index}"
67
+ )
68
+ raise SyntaxError(msg)
@@ -0,0 +1,316 @@
1
+ # pyright: reportUndefinedVariable=false
2
+ import logging
3
+ from os import environ
4
+ from sys import stdout
5
+ from typing import Any, Dict, List, Sequence
6
+ from types import ModuleType
7
+
8
+ import sly
9
+ from sly.yacc import YaccProduction
10
+
11
+ from . import expression_classes as _default_expression_classes
12
+ from .expression_classes.arrays import _ArrayType
13
+ from .lexer import ExpressionLexer
14
+
15
+
16
+ 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)
20
+
21
+ #: Gets the token list from the lexer (required)
22
+ tokens: Sequence = ExpressionLexer.tokens
23
+
24
+ #: Defines precendence of operators
25
+ precedence = (
26
+ ("left", OR), # noqa: 821
27
+ ("left", AND), # noqa: 821
28
+ ("right", NOT), # noqa: 821
29
+ ("nonassoc", EQ, NE), # noqa: 821
30
+ )
31
+
32
+ def __init__(
33
+ self,
34
+ group_ids: List[str] = [],
35
+ user_profile: Dict[str, Any] = {},
36
+ log_to_stdout: bool = False,
37
+ log_level: str = "INFO",
38
+ expression_classes: ModuleType = _default_expression_classes,
39
+ group_data: dict = {},
40
+ ) -> None:
41
+ #: A module containing classes used in expressions, such as `Arrays` or `String`
42
+ self._expression_classes = expression_classes
43
+
44
+ # Required for doing translations between group ID's and other group attributes.
45
+ # Without passing group_data into the parser all `Group` methods will return empty
46
+ if (
47
+ group_data
48
+ and hasattr(self._expression_classes, "Groups")
49
+ and type(self._expression_classes.Groups) == type
50
+ ):
51
+ self._expression_classes.Groups.group_data = group_data
52
+
53
+ self.group_data = group_data
54
+
55
+ self.__group_ids: str = group_ids
56
+ self.__user_profile: str = user_profile
57
+ self.logger: logging.Logger = logging.getLogger()
58
+
59
+ self.logger.setLevel(log_level)
60
+
61
+ if log_to_stdout:
62
+ self.logger.addHandler(logging.StreamHandler(stream=stdout))
63
+
64
+ def parse(self, expression: str):
65
+ return super().parse(ExpressionLexer().tokenize(expression))
66
+
67
+ @_("operand EQ operand") # noqa: 821
68
+ def condition(self, p: YaccProduction) -> bool:
69
+ operand0 = p.operand0
70
+ operand1 = p.operand1
71
+ return operand0 == operand1
72
+
73
+ @_("path EQ operand") # noqa: 821
74
+ def condition(self, p: YaccProduction) -> bool:
75
+ path = p.path
76
+ value = p.operand
77
+
78
+ return path == value
79
+
80
+ @_("operand NE operand") # noqa: 821
81
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
82
+ operand0 = p.operand0
83
+ operand1 = p.operand1
84
+ return operand0 != operand1
85
+
86
+ @_("operand GTE operand") # noqa: 821
87
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
88
+ operand0 = p.operand0
89
+ operand1 = p.operand1
90
+ return type(operand0) == type(operand1) and operand0 >= operand1
91
+
92
+ @_("operand LTE operand") # noqa: 821
93
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
94
+ operand0 = p.operand0
95
+ operand1 = p.operand1
96
+ return type(operand0) == type(operand1) and operand0 <= operand1
97
+
98
+ @_("operand LT operand") # noqa: 821
99
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
100
+ operand0 = p.operand0
101
+ operand1 = p.operand1
102
+ return type(operand0) == type(operand1) and operand0 < operand1
103
+
104
+ @_("operand GT operand") # noqa: 821
105
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
106
+ operand0 = p.operand0
107
+ operand1 = p.operand1
108
+ return type(operand0) == type(operand1) and operand0 > operand1
109
+
110
+ @_("condition AND condition") # noqa: 821
111
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
112
+ condition0 = p.condition0
113
+ condition1 = p.condition1
114
+ return condition0 and condition1
115
+
116
+ @_("condition OR condition") # noqa: 821
117
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
118
+ condition0 = p.condition0
119
+ condition1 = p.condition1
120
+ return condition0 or condition1
121
+
122
+ @_("NOT condition") # noqa: 821
123
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
124
+ condition = p.condition
125
+ return not condition
126
+
127
+ @_("NOT operand") # noqa: 821
128
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
129
+ return not p.operand
130
+
131
+ @_('"(" condition ")"') # noqa: 821
132
+ def condition(self, p: YaccProduction) -> bool: # noqa: 811
133
+ condition = p.condition
134
+ return condition
135
+
136
+ @_('operand "," operand') # noqa: 821
137
+ def operand(self, p: YaccProduction) -> List[Any]: # noqa: 811
138
+ operand0 = p.operand0
139
+ operand1 = p.operand1
140
+
141
+ # if not isinstance(operand0, tuple):
142
+ # return (operand0, operand1)
143
+ #
144
+ # else:
145
+ # return operand0 + (operand1,)
146
+
147
+ if isinstance(operand0, tuple) and isinstance(operand1, tuple):
148
+ res = operand0 + operand1
149
+ elif isinstance(operand0, tuple) and not isinstance(operand1, tuple):
150
+ res = operand0 + (operand1,)
151
+ elif not isinstance(operand0, tuple) and isinstance(operand1, tuple):
152
+ res = (operand0,) + operand1
153
+ else:
154
+ res = (operand0, operand1)
155
+ return res
156
+
157
+ @_("NULL")
158
+ def operand(self, p: YaccProduction) -> None:
159
+ return None
160
+
161
+ @_("INT") # noqa: 821
162
+ def operand(self, p: YaccProduction) -> int: # noqa: 811
163
+ return int(p.INT)
164
+
165
+ @_("BOOL")
166
+ def operand(self, p: YaccProduction) -> bool:
167
+ val = p.BOOL.lower()
168
+ return val == "true"
169
+
170
+ @_("array")
171
+ def operand(self, p: YaccProduction) -> List[Any]:
172
+ return _ArrayType(p.array)
173
+
174
+ @_('"{" operand "}"')
175
+ def array(self, p: YaccProduction) -> List[Any]:
176
+ if isinstance(p.operand, Sequence):
177
+ return _ArrayType(p.operand)
178
+
179
+ return _ArrayType([p.operand])
180
+
181
+ @_('array "," operand')
182
+ def operand(self, p: YaccProduction) -> List[Any]:
183
+ # if isinstance(p.operand, tuple):
184
+
185
+ if isinstance(p.operand, tuple):
186
+ return (p.array,) + p.operand
187
+
188
+ return (p.array, p.operand)
189
+
190
+ @_("STRING") # noqa: 821
191
+ def operand(self, p: YaccProduction) -> str: # noqa: 811
192
+ val = p.STRING
193
+ if val.startswith('"'):
194
+ val = val[1:-1]
195
+
196
+ return val
197
+
198
+ @_("path") # noqa: 821
199
+ def operand(self, p: YaccProduction) -> Any: # noqa: 811
200
+ return p.path
201
+
202
+ @_("USER")
203
+ def path(self, _) -> Dict[str, Any]:
204
+ return self.__user_profile
205
+
206
+ @_("NAME") # noqa: 821
207
+ def path(self, p: YaccProduction) -> Any: # noqa: 811
208
+ NAME = p.NAME
209
+
210
+ if not hasattr(p, "path"):
211
+ path = {}
212
+
213
+ return path.get(NAME) if path is not None else None
214
+
215
+ @_('path "." NAME') # noqa: 821
216
+ def operand(self, p: YaccProduction) -> Any: # noqa: 811
217
+ path = p.path
218
+ NAME = p.NAME
219
+
220
+ if isinstance(path, dict):
221
+ res = path.get(NAME)
222
+ else:
223
+ res = path
224
+
225
+ if isinstance(res, str):
226
+ res = res.replace('"', "")
227
+
228
+ return res
229
+
230
+ @_("cls")
231
+ def operand(self, p: YaccProduction) -> Any:
232
+ """
233
+ Returns a class from `self._expression_classes`
234
+ """
235
+ return p.cls
236
+
237
+ @_("CLASS")
238
+ def cls(self, p: YaccProduction) -> type:
239
+ """Returns a class from `self._expression_classes`"""
240
+
241
+ try:
242
+ return getattr(self._expression_classes, p.CLASS)
243
+ except AttributeError:
244
+ raise NotImplemented(
245
+ f"Class {p.CLASS} has not been implemented in the parser"
246
+ )
247
+
248
+ @_('cls "." NAME "(" operand ")"')
249
+ @_('cls "." NAME "(" path ")"')
250
+ def operand(self, p: YaccProduction) -> Any:
251
+ """Calls a method from `cls`, which is a class from `self._expression_classes`"""
252
+ method = getattr(p.cls, p.NAME)
253
+
254
+ if hasattr(p, "operand"):
255
+ val = p.operand
256
+ else:
257
+ val = p.path
258
+
259
+ if isinstance(val, tuple):
260
+ res = method(*val)
261
+
262
+ else:
263
+ res = method(val)
264
+
265
+ return res
266
+
267
+ @_('cls "." NAME "(" operand ")"')
268
+ @_('cls "." NAME "(" path ")"')
269
+ def condition(self, p: YaccProduction) -> bool:
270
+ """Calls a method from `cls`, which is a class from `self._expression_classes`"""
271
+ method = getattr(p.cls, p.NAME)
272
+
273
+ if hasattr(p, "operand"):
274
+ val = p.operand
275
+ else:
276
+ val = p.path
277
+
278
+ if isinstance(val, tuple):
279
+ res = method(*val)
280
+
281
+ else:
282
+ res = method(val)
283
+
284
+ return res
285
+
286
+ @_('MEMBEROFANY "(" operand ")"')
287
+ def condition(self, p: YaccProduction):
288
+ """
289
+ Tests if a user is a member of one or many groups
290
+ """
291
+ if not self.__group_ids:
292
+ return False
293
+
294
+ val = p.operand
295
+ if not isinstance(val, list):
296
+ val = [val]
297
+
298
+ res = list(set(self.__group_ids) & set(val))
299
+
300
+ return bool(res)
301
+
302
+ @_('MEMBEROF "(" operand ")"')
303
+ def condition(self, p: YaccProduction):
304
+ """Tests if a user is a member of a specific group"""
305
+
306
+ if not self.__group_ids:
307
+ return False
308
+
309
+ # return p.operand.lower() in self.__group_ids
310
+ return p.operand in self.__group_ids
311
+
312
+ @_("condition error") # noqa: 821
313
+ def operand(self, x): # noqa: 811
314
+ raise SyntaxError(
315
+ f"Unexpected token '{x.error.value}' on line {x.error.lineno}"
316
+ )
@@ -0,0 +1,21 @@
1
+ [tool.poetry]
2
+ name = "okta-expression-parser"
3
+ version = "0.2.0"
4
+ description = "Returns a boolean by passing Okta profile data to an Okta Expression using Okta Expression Language"
5
+ authors = ["Mathew Moon <me@mathewmoon.net>"]
6
+ readme = "README.md"
7
+ packages = [{include = "okta_expression_parser", from = "."}]
8
+
9
+ [tool.poetry.dependencies]
10
+ python = "^3.11"
11
+ sly = "^0.5"
12
+
13
+ [tool.poetry.group.dev.dependencies]
14
+ pytest = "7.4.3"
15
+
16
+ [tool.pytest.ini_options]
17
+ xfail_strict = true
18
+
19
+ [build-system]
20
+ requires = ["poetry-core>=1.7.0"]
21
+ build-backend = "poetry.core.masonry.api"