Skip to content

Commit df3c227

Browse files
authored
Merge pull request #69 from TencentBlueKing/develop
v1.2.1
2 parents 965f436 + 19f3ef9 commit df3c227

File tree

12 files changed

+236
-149
lines changed

12 files changed

+236
-149
lines changed

iam/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# -*- coding: utf-8 -*-
22

3-
__version__ = "1.2.0"
3+
__version__ = "1.2.1"

iam/contrib/converter/queryset.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@
1212

1313

1414
import operator
15-
from six.moves import reduce
1615

1716
from django.db.models import Q
18-
1917
from iam.eval.constants import KEYWORD_BK_IAM_PATH_FIELD_SUFFIX, OP
2018
from iam.eval.expression import field_value_convert
19+
from six.moves import reduce
2120

2221
from .base import Converter
2322

@@ -84,6 +83,9 @@ def _ends_with(self, left, right):
8483
def _not_ends_with(self, left, right):
8584
return self._negative("{}__endswith", left, right)
8685

86+
def _string_contains(self, left, right):
87+
return self._positive("{}__contains", left, right)
88+
8789
def _lt(self, left, right):
8890
return self._positive("{}__lt", left, right)
8991

@@ -139,6 +141,7 @@ def convert(self, data):
139141
OP.NOT_STARTS_WITH: self._not_starts_with,
140142
OP.ENDS_WITH: self._ends_with,
141143
OP.NOT_ENDS_WITH: self._not_ends_with,
144+
OP.STRING_CONTAINS: self._string_contains,
142145
OP.LT: self._lt,
143146
OP.LTE: self._lte,
144147
OP.GT: self._gt,

iam/contrib/converter/sql.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313

1414
import six
15-
1615
from iam.eval.constants import OP
1716
from iam.eval.expression import field_value_convert
1817

@@ -75,12 +74,12 @@ def _not_eq(self, left, right):
7574
return self._negative("{} != {}", left, right)
7675

7776
def _in(self, left, right):
78-
# TODO: right shuld be a list
77+
# TODO: right should be a list
7978
right = [self._to_str_present(r, True) for r in right]
8079
return "{} IN ({})".format(left, ",".join([str(r) for r in right]))
8180

8281
def _not_in(self, left, right):
83-
# TODO: right shuld be a list
82+
# TODO: right should be a list
8483
right = [self._to_str_present(r, True) for r in right]
8584
return "{} NOT IN ({})".format(left, ",".join([str(r) for r in right]))
8685

@@ -103,6 +102,9 @@ def _ends_with(self, left, right):
103102
def _not_ends_with(self, left, right):
104103
return self._negative("{} NOT LIKE '%{}'", left, right, False)
105104

105+
def _string_contains(self, left, right):
106+
return self._positive("{} LIKE '%{}%'", left, right, False)
107+
106108
def _lt(self, left, right):
107109
return self._positive("{} < {}", left, right)
108110

@@ -145,6 +147,7 @@ def convert(self, data):
145147
OP.NOT_STARTS_WITH: self._not_starts_with,
146148
OP.ENDS_WITH: self._ends_with,
147149
OP.NOT_ENDS_WITH: self._not_ends_with,
150+
OP.STRING_CONTAINS: self._string_contains,
148151
OP.LT: self._lt,
149152
OP.LTE: self._lte,
150153
OP.GT: self._gt,

iam/eval/constants.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class OP(object):
3434
ENDS_WITH = "ends_with"
3535
NOT_ENDS_WITH = "not_ends_with"
3636

37+
STRING_CONTAINS = "string_contains"
38+
3739
LT = "lt"
3840
LTE = "lte"
3941
GT = "gt"
@@ -47,12 +49,13 @@ class OP(object):
4749
NOT_EQ,
4850
IN,
4951
NOT_IN,
50-
CONTAINS,
51-
NOT_CONTAINS,
52+
# CONTAINS,
53+
# NOT_CONTAINS,
5254
STARTS_WITH,
5355
NOT_STARTS_WITH,
5456
ENDS_WITH,
5557
NOT_ENDS_WITH,
58+
STRING_CONTAINS,
5659
ANY,
5760
],
5861
"numberic": [EQ, NOT_EQ, IN, NOT_IN, LT, LTE, GT, GTE],

iam/eval/operators.py

Lines changed: 82 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def expr(self):
111111
def calculate(self, left, right):
112112
pass
113113

114-
def _eval_positive(self, attr, attr_is_array, value, value_is_array): # NOQA
114+
def _eval_positive(self, object_attr, is_object_attr_array, policy_value): # NOQA
115115
"""
116116
positive:
117117
- 1 hit: return True
@@ -121,65 +121,22 @@ def _eval_positive(self, attr, attr_is_array, value, value_is_array): # NOQA
121121
op = eq => one of attr equals one of value
122122
123123
attr = 1; value = 1; True
124-
attr = 1; value = [1, 2]; True
125124
attr = [1, 2]; value = 2; True
126-
attr = [1, 2]; value = [5, 1]; True
127-
128-
attr = [1, 2]; value = [3, 4]; False
129125
"""
130-
if self.op == OP.ANY:
131-
return self.calculate(attr, value)
132-
133-
# 1. IN/NOT_IN value is a list, just only check attr
134-
if self.op in (OP.IN,):
135-
if attr_is_array:
136-
for a in attr:
137-
if self.calculate(a, value):
138-
return True
139-
return False
140-
141-
return self.calculate(attr, value)
142-
143-
# 2. CONTAINS/NOT_CONTAINS attr is a list, just check value
144-
if self.op in (OP.CONTAINS,):
145-
if value_is_array:
146-
for v in value:
147-
if self.calculate(attr, v):
148-
return True
149-
return False
150-
151-
return self.calculate(attr, value)
152-
153-
# 3. Others, check both attr and value
154-
# 3.1 both not array, the most common situation
155-
if not (value_is_array or attr_is_array):
156-
return self.calculate(attr, value)
157-
158-
# 3.2 only value is array, the second common situation
159-
if value_is_array and (not attr_is_array):
160-
for v in value:
161-
# return early if hit
162-
if self.calculate(attr, v):
126+
# if self.op == OP.ANY:
127+
# return self.calculate(object_attr, policy_value)
128+
129+
# NOTE: here, the policyValue should not be array!
130+
# It's single value (except: the NotIn op policyValue is an array)
131+
if is_object_attr_array:
132+
for a in object_attr:
133+
if self.calculate(a, policy_value):
163134
return True
164135
return False
165136

166-
# 3.3 only attr value is array
167-
if (not value_is_array) and attr_is_array:
168-
for a in attr:
169-
# return early if hit
170-
if self.calculate(a, value):
171-
return True
172-
return False
137+
return self.calculate(object_attr, policy_value)
173138

174-
# 4. both array
175-
for a in attr:
176-
for v in value:
177-
# return early if hit
178-
if self.calculate(a, v):
179-
return True
180-
return False
181-
182-
def _eval_negative(self, attr, attr_is_array, value, value_is_array): # NOQA
139+
def _eval_negative(self, object_attr, is_object_attr_array, policy_value): # NOQA
183140
"""
184141
negative:
185142
- 1 miss: return False
@@ -189,58 +146,18 @@ def _eval_negative(self, attr, attr_is_array, value, value_is_array): # NOQA
189146
op = not_eq => all of attr should not_eq to all of the value
190147
191148
attr = 1; value = 2; True
192-
attr = 1; value = [2]; True
193-
attr = [1, 2]; value = [3, 4]; True
194149
attr = [1, 2]; value = 3; True
195-
196-
attr = [1, 2]; value = [2, 3]; False
197150
"""
198-
# 1. IN/NOT_IN value is a list, just only check attr
199-
if self.op in (OP.NOT_IN,):
200-
if attr_is_array:
201-
for a in attr:
202-
if not self.calculate(a, value):
203-
return False
204-
return True
205-
206-
return self.calculate(attr, value)
207-
208-
# 2. CONTAINS/NOT_CONTAINS attr is a list, just check value
209-
if self.op in (OP.NOT_CONTAINS,):
210-
if value_is_array:
211-
for v in value:
212-
if not self.calculate(attr, v):
213-
return False
214-
return True
215-
216-
return self.calculate(attr, value)
217-
218-
# 3. Others, check both attr and value
219-
# 3.1 both not array, the most common situation
220-
if not (value_is_array or attr_is_array):
221-
return self.calculate(attr, value)
222151

223-
# 3.2 only value is array, the second common situation
224-
if value_is_array and (not attr_is_array):
225-
for v in value:
226-
if not self.calculate(attr, v):
152+
# NOTE: here, the policyValue should not be array!
153+
# It's single value (except: the NotIn op policyValue is an array)
154+
if is_object_attr_array:
155+
for a in object_attr:
156+
if not self.calculate(a, policy_value):
227157
return False
228158
return True
229159

230-
# 3.3 only attr value is array
231-
if (not value_is_array) and attr_is_array:
232-
for a in attr:
233-
if not self.calculate(a, value):
234-
return False
235-
return True
236-
237-
# 4. both array
238-
for a in attr:
239-
for v in value:
240-
# return early if hit
241-
if not self.calculate(a, v):
242-
return False
243-
return True
160+
return self.calculate(object_attr, policy_value)
244161

245162
def eval(self, obj_set):
246163
"""
@@ -251,19 +168,64 @@ def eval(self, obj_set):
251168
if one of them is array, or both array
252169
calculate each item in array
253170
"""
254-
attr = obj_set.get(self.field)
255-
value = self.value
171+
object_attr = obj_set.get(self.field)
172+
policy_value = self.value
256173

257-
attr_is_array = isinstance(attr, (list, tuple))
258-
value_is_array = isinstance(value, (list, tuple))
174+
is_object_attr_array = isinstance(object_attr, (list, tuple))
175+
is_policy_value_array = isinstance(policy_value, (list, tuple))
259176

260-
# positive and negative operator
261-
# == 命中一个即返回
262-
# != 需要全部遍历完, 确认全部不等于才返回?
263-
if self.op.startswith("not_"):
264-
return self._eval_negative(attr, attr_is_array, value, value_is_array)
265-
else:
266-
return self._eval_positive(attr, attr_is_array, value, value_is_array)
177+
# any
178+
if self.op == OP.ANY:
179+
return True
180+
181+
# if you add new operator, please read this first: https://github.com/TencentBlueKing/bk-iam-saas/issues/1293
182+
# valid the attr and value first
183+
if self.op in (OP.IN, OP.NOT_IN):
184+
# a in b, a not_in b
185+
# b should be an array, while a can be a single or an array
186+
# so we should make the in expression b always be an array
187+
if not is_policy_value_array:
188+
return False
189+
190+
if self.op == OP.IN:
191+
return self._eval_positive(object_attr, is_object_attr_array, policy_value)
192+
else:
193+
return self._eval_negative(object_attr, is_object_attr_array, policy_value)
194+
195+
if self.op in (OP.CONTAINS, OP.NOT_CONTAINS):
196+
# a contains b, a not_contains b
197+
# a should be an array, b should be a single value
198+
# so, we should make the contains expression b always be a single string,
199+
# while a can be a single value or an array
200+
if not is_object_attr_array or is_policy_value_array:
201+
return False
202+
return self.calculate(object_attr, policy_value)
203+
204+
if self.op in (
205+
OP.EQ,
206+
OP.NOT_EQ,
207+
OP.LT,
208+
OP.LTE,
209+
OP.GT,
210+
OP.GTE,
211+
OP.STARTS_WITH,
212+
OP.NOT_STARTS_WITH,
213+
OP.ENDS_WITH,
214+
OP.NOT_ENDS_WITH,
215+
OP.STRING_CONTAINS,
216+
):
217+
# a starts_with b, a not_starts_with, a ends_with b, a not_ends_with b
218+
# b should be a single value, while a can be a single value or an array
219+
if is_policy_value_array:
220+
return False
221+
222+
# positive and negative operator
223+
# == 命中一个即返回
224+
# != 需要全部遍历完, 确认全部不等于才返回?
225+
if self.op.startswith("not_"):
226+
return self._eval_negative(object_attr, is_object_attr_array, policy_value)
227+
else:
228+
return self._eval_positive(object_attr, is_object_attr_array, policy_value)
267229

268230

269231
class EqualOperator(BinaryOperator):
@@ -318,7 +280,6 @@ def calculate(self, left, right):
318280

319281
class StartsWithOperator(BinaryOperator):
320282
def __init__(self, field, value):
321-
# TODO: value should be string?
322283
super(StartsWithOperator, self).__init__(OP.STARTS_WITH, field, value)
323284

324285
def calculate(self, left, right):
@@ -327,7 +288,6 @@ def calculate(self, left, right):
327288

328289
class NotStartsWithOperator(BinaryOperator):
329290
def __init__(self, field, value):
330-
# TODO: value should be string?
331291
super(NotStartsWithOperator, self).__init__(OP.NOT_STARTS_WITH, field, value)
332292

333293
def calculate(self, left, right):
@@ -336,7 +296,6 @@ def calculate(self, left, right):
336296

337297
class EndsWithOperator(BinaryOperator):
338298
def __init__(self, field, value):
339-
# TODO: value should be string?
340299
super(EndsWithOperator, self).__init__(OP.ENDS_WITH, field, value)
341300

342301
def calculate(self, left, right):
@@ -345,13 +304,20 @@ def calculate(self, left, right):
345304

346305
class NotEndsWithOperator(BinaryOperator):
347306
def __init__(self, field, value):
348-
# TODO: value should be string?
349307
super(NotEndsWithOperator, self).__init__(OP.NOT_ENDS_WITH, field, value)
350308

351309
def calculate(self, left, right):
352310
return not left.endswith(right)
353311

354312

313+
class StringContainsOperator(BinaryOperator):
314+
def __init__(self, field, value):
315+
super(StringContainsOperator, self).__init__(OP.STRING_CONTAINS, field, value)
316+
317+
def calculate(self, left, right):
318+
return right in left
319+
320+
355321
class LTOperator(BinaryOperator):
356322
def __init__(self, field, value):
357323
# TODO: field / value should be numberic
@@ -407,6 +373,7 @@ def calculate(self, left, right):
407373
OP.NOT_STARTS_WITH: NotStartsWithOperator,
408374
OP.ENDS_WITH: EndsWithOperator,
409375
OP.NOT_ENDS_WITH: NotEndsWithOperator,
376+
OP.STRING_CONTAINS: StringContainsOperator,
410377
OP.LT: LTOperator,
411378
OP.LTE: LTEOperator,
412379
OP.GT: GTOperator,

0 commit comments

Comments
 (0)