1 | """NDG Security Rule type definition |
---|
2 | |
---|
3 | NERC DataGrid |
---|
4 | """ |
---|
5 | __author__ = "P J Kershaw" |
---|
6 | __date__ = "25/02/10" |
---|
7 | __copyright__ = "(C) 2010 Science and Technology Facilities Council" |
---|
8 | __contact__ = "Philip.Kershaw@stfc.ac.uk" |
---|
9 | __license__ = "BSD - see LICENSE file in top-level directory" |
---|
10 | __contact__ = "Philip.Kershaw@stfc.ac.uk" |
---|
11 | __revision__ = "$Id$" |
---|
12 | import traceback |
---|
13 | import logging |
---|
14 | log = logging.getLogger(__name__) |
---|
15 | |
---|
16 | from ndg.xacml.core import XacmlCoreBase |
---|
17 | from ndg.xacml.core.target import Target |
---|
18 | from ndg.xacml.core.condition import Condition |
---|
19 | from ndg.xacml.core.context.result import Decision |
---|
20 | |
---|
21 | |
---|
22 | class Effect(object): |
---|
23 | """Rule Effect |
---|
24 | |
---|
25 | @cvar PERMIT_STR: permit decision string |
---|
26 | @type PERMIT_STR: string |
---|
27 | |
---|
28 | @cvar DENY_STR: deny decision string |
---|
29 | @type DENY_STR: string |
---|
30 | |
---|
31 | @cvar TYPES: list of valid effect strings |
---|
32 | @type TYPES: tuple |
---|
33 | |
---|
34 | @ivar value: effect value |
---|
35 | @type value: string |
---|
36 | """ |
---|
37 | DENY_STR = 'Deny' |
---|
38 | PERMIT_STR = 'Permit' |
---|
39 | TYPES = (DENY_STR, PERMIT_STR) |
---|
40 | __slots__ = ('__value',) |
---|
41 | |
---|
42 | def __init__(self, effect=DENY_STR): |
---|
43 | """@param effect: initialise effect value, defaults to deny |
---|
44 | @type effect: basestring / ndg.xacml.core.rule.Effect |
---|
45 | """ |
---|
46 | self.__value = None |
---|
47 | self.value = effect |
---|
48 | |
---|
49 | def __getstate__(self): |
---|
50 | '''Enable pickling |
---|
51 | |
---|
52 | @return: class instance attributes dictionary |
---|
53 | @rtype: dict |
---|
54 | ''' |
---|
55 | _dict = {} |
---|
56 | for attrName in Effect.__slots__: |
---|
57 | # Ugly hack to allow for derived classes setting private member |
---|
58 | # variables |
---|
59 | if attrName.startswith('__'): |
---|
60 | attrName = "_Effect" + attrName |
---|
61 | |
---|
62 | _dict[attrName] = getattr(self, attrName) |
---|
63 | |
---|
64 | return _dict |
---|
65 | |
---|
66 | def __setstate__(self, attrDict): |
---|
67 | '''Enable pickling |
---|
68 | |
---|
69 | @param attrDict: class instance attributes dictionary |
---|
70 | @type attrDict: dict |
---|
71 | ''' |
---|
72 | for attrName, val in attrDict.items(): |
---|
73 | setattr(self, attrName, val) |
---|
74 | |
---|
75 | def _setValue(self, value): |
---|
76 | """Set effect value |
---|
77 | |
---|
78 | @param value: effect value - constrained vocabulary to Effect.TYPES |
---|
79 | @type value: string or ndg.xacml.core.rule.Effect |
---|
80 | @raise AttributeError: invalid decision string value input |
---|
81 | @raise TypeError: invalid type for input decision value |
---|
82 | """ |
---|
83 | if isinstance(value, Effect): |
---|
84 | # Cast to string |
---|
85 | value = str(value) |
---|
86 | |
---|
87 | elif not isinstance(value, basestring): |
---|
88 | raise TypeError('Expecting string or Effect instance for ' |
---|
89 | '"value" attribute; got %r instead' % type(value)) |
---|
90 | |
---|
91 | if value not in self.__class__.TYPES: |
---|
92 | raise AttributeError('Permissable effect types are %r; got ' |
---|
93 | '%r instead' % (Effect.TYPES, value)) |
---|
94 | self.__value = value |
---|
95 | |
---|
96 | def _getValue(self): |
---|
97 | """Get effect value |
---|
98 | |
---|
99 | @return: effect value |
---|
100 | @rtype: string |
---|
101 | """ |
---|
102 | return self.__value |
---|
103 | |
---|
104 | value = property(fget=_getValue, fset=_setValue, doc="Effect value") |
---|
105 | |
---|
106 | def __str__(self): |
---|
107 | """represent decision as a string |
---|
108 | |
---|
109 | @return: decision value |
---|
110 | @rtype: string |
---|
111 | """ |
---|
112 | return self.__value |
---|
113 | |
---|
114 | def __eq__(self, effect): |
---|
115 | """ |
---|
116 | @param effect: effect value to compare with self's |
---|
117 | @type effect: string or ndg.xacml.core.rule.Effect |
---|
118 | @return: True if the decision values match, False otherwise |
---|
119 | @rtype: bool |
---|
120 | @raise AttributeError: invalid decision string value input |
---|
121 | @raise TypeError: invalid type for input decision value |
---|
122 | """ |
---|
123 | if isinstance(effect, Effect): |
---|
124 | # Cast to string |
---|
125 | value = effect.value |
---|
126 | |
---|
127 | elif isinstance(effect, basestring): |
---|
128 | value = effect |
---|
129 | |
---|
130 | else: |
---|
131 | raise TypeError('Expecting string or Effect instance for ' |
---|
132 | 'input effect value; got %r instead' % type(value)) |
---|
133 | |
---|
134 | if value not in self.__class__.TYPES: |
---|
135 | raise AttributeError('Permissable effect types are %r; got ' |
---|
136 | '%r instead' % (Effect.TYPES, value)) |
---|
137 | |
---|
138 | return self.__value == value |
---|
139 | |
---|
140 | def __nonzero__(self): |
---|
141 | """Boolean evaluation of a rule effect - True = Allow; False = Deny |
---|
142 | |
---|
143 | @return: True if the effect value is permit, False otherwise |
---|
144 | @rtype: bool |
---|
145 | """ |
---|
146 | return self.__value == Effect.PERMIT_STR |
---|
147 | |
---|
148 | |
---|
149 | class PermitEffect(Effect): |
---|
150 | """Permit authorisation Effect""" |
---|
151 | __slots__ = () |
---|
152 | |
---|
153 | def __init__(self): |
---|
154 | """Initialise set with Permit value""" |
---|
155 | super(PermitEffect, self).__init__(Effect.PERMIT_STR) |
---|
156 | |
---|
157 | def _setValue(self, value): |
---|
158 | """Make value read-only |
---|
159 | @raise AttributeError: value can't be set |
---|
160 | """ |
---|
161 | raise AttributeError("can't set attribute") |
---|
162 | |
---|
163 | |
---|
164 | class DenyEffect(Effect): |
---|
165 | """Deny authorisation Effect""" |
---|
166 | __slots__ = () |
---|
167 | |
---|
168 | def __init__(self): |
---|
169 | """Initialise set with Permit value""" |
---|
170 | super(DenyEffect, self).__init__(Effect.DENY_STR) |
---|
171 | |
---|
172 | def _setValue(self, value): |
---|
173 | """Make value read-only |
---|
174 | @raise AttributeError: value can't be set |
---|
175 | """ |
---|
176 | raise AttributeError("can't set attribute") |
---|
177 | |
---|
178 | # Add instances of each for convenience |
---|
179 | Effect.PERMIT = PermitEffect() |
---|
180 | Effect.DENY = DenyEffect() |
---|
181 | |
---|
182 | |
---|
183 | class Rule(XacmlCoreBase): |
---|
184 | """XACML Policy Rule |
---|
185 | |
---|
186 | @cvar ELEMENT_LOCAL_NAME: XML local name for this element |
---|
187 | @type ELEMENT_LOCAL_NAME: string |
---|
188 | @cvar DESCRIPTION_LOCAL_NAME: XML local name for the description element |
---|
189 | @type DESCRIPTION_LOCAL_NAME: string |
---|
190 | @cvar RULE_ID_ATTRIB_NAME: rule id XML attribute name |
---|
191 | @type RULE_ID_ATTRIB_NAME: string |
---|
192 | @cvar EFFECT_ATTRIB_NAME: effect XML attribute name |
---|
193 | @type EFFECT_ATTRIB_NAME: string |
---|
194 | |
---|
195 | @ivar __target: rule target |
---|
196 | @type __target: ndg.xacml.core.target.Target / NoneType |
---|
197 | @ivar __condition: rule condition |
---|
198 | @type __condition: ndg.xacml.core.condition.Condition / NoneType |
---|
199 | @ivar __description: rule description text |
---|
200 | @type __description: basestring / NoneType |
---|
201 | @ivar __id: rule ID |
---|
202 | @type __id: basestring / NoneType |
---|
203 | @ivar __effect: rule effect |
---|
204 | @type __effect: ndg.xacml.core.rule.Effect / NoneType |
---|
205 | """ |
---|
206 | ELEMENT_LOCAL_NAME = 'Rule' |
---|
207 | RULE_ID_ATTRIB_NAME = 'RuleId' |
---|
208 | EFFECT_ATTRIB_NAME = 'Effect' |
---|
209 | |
---|
210 | DESCRIPTION_LOCAL_NAME = 'Description' |
---|
211 | |
---|
212 | __slots__ = ( |
---|
213 | '__target', |
---|
214 | '__condition', |
---|
215 | '__description', |
---|
216 | '__id', |
---|
217 | '__effect' |
---|
218 | ) |
---|
219 | |
---|
220 | def __init__(self): |
---|
221 | """Initialise attributes""" |
---|
222 | super(Rule, self).__init__() |
---|
223 | |
---|
224 | self.__id = None |
---|
225 | self.__effect = None |
---|
226 | self.__target = None |
---|
227 | self.__condition = None |
---|
228 | |
---|
229 | @property |
---|
230 | def target(self): |
---|
231 | """Get Rule target |
---|
232 | @return: rule target |
---|
233 | @rtype: ndg.xacml.core.target import Target / NoneType |
---|
234 | """ |
---|
235 | return self.__target |
---|
236 | |
---|
237 | @target.setter |
---|
238 | def target(self, value): |
---|
239 | """Set rule target |
---|
240 | @param value: rule target |
---|
241 | @type value: ndg.xacml.core.target import Target |
---|
242 | @raise TypeError: incorrect type set |
---|
243 | """ |
---|
244 | if not isinstance(value, Target): |
---|
245 | raise TypeError('Expecting %r type for "id" ' |
---|
246 | 'attribute; got %r' % (Target, type(value))) |
---|
247 | self.__target = value |
---|
248 | |
---|
249 | @property |
---|
250 | def condition(self): |
---|
251 | """Get rule condition |
---|
252 | |
---|
253 | @return: rule condition |
---|
254 | @rtype: ndg.xacml.core.condition.Condition / NoneType |
---|
255 | """ |
---|
256 | return self.__condition |
---|
257 | |
---|
258 | @condition.setter |
---|
259 | def condition(self, value): |
---|
260 | """Set rule condition |
---|
261 | |
---|
262 | @param value: rule condition |
---|
263 | @type value: ndg.xacml.core.condition.Condition |
---|
264 | @raise TypeError: incorrect type set |
---|
265 | """ |
---|
266 | if not isinstance(value, Condition): |
---|
267 | raise TypeError('Expecting %r type for "id" attribute; got %r' % |
---|
268 | (Condition, type(value))) |
---|
269 | |
---|
270 | self.__condition = value |
---|
271 | |
---|
272 | def _get_id(self): |
---|
273 | """Get rule ID |
---|
274 | |
---|
275 | @return: rule ID |
---|
276 | @rtype: ndg.xacml.core.condition.Condition / NoneType |
---|
277 | """ |
---|
278 | return self.__id |
---|
279 | |
---|
280 | def _set_id(self, value): |
---|
281 | """Set rule ID |
---|
282 | |
---|
283 | @param value: rule ID |
---|
284 | @type value: basestring |
---|
285 | @raise TypeError: incorrect type set |
---|
286 | """ |
---|
287 | if not isinstance(value, basestring): |
---|
288 | raise TypeError('Expecting %r type for "id" attribute; got %r' % |
---|
289 | (basestring, type(value))) |
---|
290 | |
---|
291 | self.__id = value |
---|
292 | |
---|
293 | id = property(_get_id, _set_id, None, "Rule identifier attribute") |
---|
294 | |
---|
295 | def _get_effect(self): |
---|
296 | """Get rule effect |
---|
297 | |
---|
298 | @return: rule effect |
---|
299 | @rtype: ndg.xacml.core.rule.Effect / NoneType |
---|
300 | """ |
---|
301 | return self.__effect |
---|
302 | |
---|
303 | def _set_effect(self, value): |
---|
304 | """Set rule effect |
---|
305 | |
---|
306 | @param value: rule effect |
---|
307 | @type value: ndg.xacml.core.rule.Effect |
---|
308 | @raise TypeError: incorrect type set |
---|
309 | """ |
---|
310 | if not isinstance(value, Effect): |
---|
311 | raise TypeError('Expecting %r type for "effect" ' |
---|
312 | 'attribute; got %r' % (Effect, type(value))) |
---|
313 | |
---|
314 | self.__effect = value |
---|
315 | |
---|
316 | effect = property(_get_effect, _set_effect, None, |
---|
317 | "Rule effect attribute") |
---|
318 | |
---|
319 | def _getDescription(self): |
---|
320 | """Get rule description |
---|
321 | |
---|
322 | @return: rule description |
---|
323 | @rtype: basestring / NoneType |
---|
324 | """ |
---|
325 | return self.__description |
---|
326 | |
---|
327 | def _setDescription(self, value): |
---|
328 | """Set rule description |
---|
329 | |
---|
330 | @param value: rule description |
---|
331 | @type value: basestring |
---|
332 | @raise TypeError: incorrect type set |
---|
333 | """ |
---|
334 | if not isinstance(value, basestring): |
---|
335 | raise TypeError('Expecting string type for "description" ' |
---|
336 | 'attribute; got %r' % type(value)) |
---|
337 | self.__description = value |
---|
338 | |
---|
339 | description = property(_getDescription, _setDescription, |
---|
340 | doc="Rule Description text") |
---|
341 | |
---|
342 | def evaluate(self, context): |
---|
343 | """Evaluate a rule |
---|
344 | @param context: the request context |
---|
345 | @type context: ndg.xacml.core.request.Request |
---|
346 | @return: result of the evaluation - the decision for this rule |
---|
347 | @rtype: ndg.xacml.core.context.result.Decision |
---|
348 | """ |
---|
349 | |
---|
350 | # Place exception block to enable rule combining algorithm which calls |
---|
351 | # this method to correctly handle Indeterminate results |
---|
352 | try: |
---|
353 | log.debug('Evaluating rule %r ...', self.id) |
---|
354 | |
---|
355 | # Instantiation implicitly sets to default value of Indeterminate |
---|
356 | decision = Decision() |
---|
357 | |
---|
358 | # Check for a rule target |
---|
359 | if self.target is not None: |
---|
360 | targetMatch = self.target.match(context) |
---|
361 | if targetMatch: |
---|
362 | log.debug('Match to request context for target in rule ' |
---|
363 | '%r', self.id) |
---|
364 | else: |
---|
365 | log.debug('No target set in rule %r', self.id) |
---|
366 | targetMatch = True |
---|
367 | |
---|
368 | if not targetMatch: |
---|
369 | log.debug('No match to request context for target in rule ' |
---|
370 | '%r returning NotApplicable status', self.id) |
---|
371 | decision = Decision.NOT_APPLICABLE |
---|
372 | return decision |
---|
373 | |
---|
374 | # Apply the condition if present |
---|
375 | if self.condition is not None: |
---|
376 | conditionStatus = self.condition.evaluate(context) |
---|
377 | else: |
---|
378 | # No condition set to True - 7.8 in spec.: |
---|
379 | # |
---|
380 | # The condition value SHALL be "True" if the <Condition> element |
---|
381 | # is absent |
---|
382 | log.debug('No condition set for rule %r: setting condition ' |
---|
383 | 'status True', self.id) |
---|
384 | conditionStatus = True |
---|
385 | |
---|
386 | # Ref. Spec. 7.9 Rule evaluation, Nb. to get this far, the target |
---|
387 | # must evaluated as True |
---|
388 | if conditionStatus: |
---|
389 | decision = Decision(decision=self.effect.value) |
---|
390 | else: |
---|
391 | decision = Decision.NOT_APPLICABLE |
---|
392 | |
---|
393 | return decision |
---|
394 | |
---|
395 | except Exception, e: |
---|
396 | log.error('Error occurred evaluating rule %r, returning ' |
---|
397 | 'Indeterminate result to caller: %s', |
---|
398 | self.id, |
---|
399 | traceback.format_exc()) |
---|
400 | return Decision.INDETERMINATE |
---|