1 | """NDG Security Policy Decision Point type definition |
---|
2 | |
---|
3 | NERC DataGrid Project |
---|
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 logging |
---|
13 | log = logging.getLogger(__name__) |
---|
14 | |
---|
15 | import traceback |
---|
16 | |
---|
17 | from ndg.xacml.core.context.pdpinterface import PDPInterface |
---|
18 | from ndg.xacml.core.policy import Policy |
---|
19 | from ndg.xacml.core.context.request import Request |
---|
20 | from ndg.xacml.core.context.response import Response |
---|
21 | from ndg.xacml.core.context.result import Result, Decision |
---|
22 | from ndg.xacml.core.functions import FunctionMap |
---|
23 | from ndg.xacml.parsers import AbstractReader |
---|
24 | |
---|
25 | |
---|
26 | class PDP(PDPInterface): |
---|
27 | """A XACML Policy Decision Point implementation. It supports the use of a |
---|
28 | single policy but not policy sets |
---|
29 | """ |
---|
30 | __slots__ = ('__policy',) |
---|
31 | TARGET_CHILD_ATTRS = ('subjects', 'resources', 'actions', 'environments') |
---|
32 | |
---|
33 | def __init__(self, policy=None): |
---|
34 | """ |
---|
35 | @param policy: policy object for PDP to use to apply access control |
---|
36 | decisions, may be omitted. |
---|
37 | @type policy: ndg.xacml.core.policy.Policy / None |
---|
38 | """ |
---|
39 | self.__policy = None |
---|
40 | if policy is not None: |
---|
41 | self.policy = policy |
---|
42 | |
---|
43 | self.matchFunc = FunctionMap.withLoadedMap() |
---|
44 | |
---|
45 | |
---|
46 | @classmethod |
---|
47 | def fromPolicySource(cls, source, readerFactory): |
---|
48 | """Create a new PDP instance with a given policy |
---|
49 | @param source: source for policy |
---|
50 | @type source: type (dependent on the reader set, it could be for example |
---|
51 | a file path string, file object, XML element instance) |
---|
52 | @param readerFactory: reader factory returns the reader to use to read |
---|
53 | this policy |
---|
54 | @type readerFactory: ndg.xacml.parsers.AbstractReader derived type |
---|
55 | """ |
---|
56 | pdp = cls() |
---|
57 | pdp.policy = Policy.fromSource(source, readerFactory) |
---|
58 | return pdp |
---|
59 | |
---|
60 | @property |
---|
61 | def policy(self): |
---|
62 | """policy object for PDP to use to apply access control decisions""" |
---|
63 | return self.__policy |
---|
64 | |
---|
65 | @policy.setter |
---|
66 | def policy(self, value): |
---|
67 | '''policy object for PDP to use to apply access control decisions''' |
---|
68 | if not isinstance(value, Policy): |
---|
69 | raise TypeError('Expecting %r derived type for "policy" input; got ' |
---|
70 | '%r instead' % (Policy, type(value))) |
---|
71 | self.__policy = value |
---|
72 | |
---|
73 | def evaluate(self, request): |
---|
74 | """Make an access control decision for the given request based on the |
---|
75 | policy set |
---|
76 | |
---|
77 | @param request: XACML request context |
---|
78 | @type request: ndg.xacml.core.context.request.Request |
---|
79 | @return: XACML response instance |
---|
80 | @rtype: ndg.xacml.core.context.response.Response |
---|
81 | """ |
---|
82 | response = Response() |
---|
83 | result = Result() |
---|
84 | response.results.append(result) |
---|
85 | result.decision = Decision.NOT_APPLICABLE |
---|
86 | |
---|
87 | if not isinstance(request, Request): |
---|
88 | log.error('Expecting %r derived type for "reader" input; got ' |
---|
89 | '%r instead' % Request, type(request)) |
---|
90 | result.decision = Decision.INDETERMINATE |
---|
91 | return response |
---|
92 | |
---|
93 | # Exception block around all rule processing in order to set |
---|
94 | # INDETERMINATE response from any exceptions raised |
---|
95 | try: |
---|
96 | log.debug('Checking policy target for match...') |
---|
97 | |
---|
98 | if not self.matchTarget(self.policy.target, request): |
---|
99 | log.debug('No match for policy target setting Decision=%s', |
---|
100 | Decision.NOT_APPLICABLE_STR) |
---|
101 | |
---|
102 | result.decision = Decision.NOT_APPLICABLE |
---|
103 | return response |
---|
104 | |
---|
105 | # Check rules |
---|
106 | for rule in self.policy.rules: |
---|
107 | log.debug('Checking policy rule %r for match...', rule.id) |
---|
108 | if not self.matchTarget(rule.target, request): |
---|
109 | log.debug('No match to request context for target in rule ' |
---|
110 | '%r', rule.id) |
---|
111 | continue |
---|
112 | except: |
---|
113 | log.error('Exception raised evaluating request context, returning ' |
---|
114 | 'Decision=%s:%s', |
---|
115 | Decision.INDETERMINATE_STR, |
---|
116 | traceback.format_exc()) |
---|
117 | result.decision = Decision.INDETERMINATE |
---|
118 | |
---|
119 | return response |
---|
120 | |
---|
121 | def matchTarget(self, target, request): |
---|
122 | """Generic method to match a <Target> element to the request context |
---|
123 | |
---|
124 | @param target: XACML target element |
---|
125 | @type target: ndg.xacml.core.target.Target |
---|
126 | @param request: XACML request context |
---|
127 | @type request: ndg.xacml.core.context.request.Request |
---|
128 | @return: True if request context matches the given target, |
---|
129 | False otherwise |
---|
130 | @rtype: bool |
---|
131 | """ |
---|
132 | if target is None: |
---|
133 | log.debug('No target set so no match with request context') |
---|
134 | return False |
---|
135 | |
---|
136 | # From section 5.5 of the XACML 2.0 Core Spec: |
---|
137 | # |
---|
138 | # For the parent of the <Target> element to be applicable to the |
---|
139 | # decision request, there MUST be at least one positive match between |
---|
140 | # each section of the <Target> element and the corresponding section of |
---|
141 | # the <xacml-context:Request> element. |
---|
142 | for i in self.__class__.TARGET_CHILD_ATTRS: |
---|
143 | for targetChild in getattr(target, i): |
---|
144 | for requestChild in getattr(request, i): |
---|
145 | if self.matchTargetChild(targetChild, requestChild): |
---|
146 | return True |
---|
147 | |
---|
148 | return False |
---|
149 | |
---|
150 | def matchTargetChild(self, targetChild, requestChild): |
---|
151 | """Match a child (Subject, Resource, Action or Environment) from the |
---|
152 | request context with a given target's child |
---|
153 | |
---|
154 | @param targetChild: Target Subject, Resource, Action or Environment |
---|
155 | object |
---|
156 | @type targetChild: ndg.xacml.core.TargetChildBase |
---|
157 | @param requestChild: Request Subject, Resource, Action or Environment |
---|
158 | object |
---|
159 | @type requestChild: ndg.xacml.core.context.RequestChildBase |
---|
160 | @return: True if request context matches something in the target |
---|
161 | @rtype: bool |
---|
162 | @raise NotImplementedError: AttributeSelector processing is not |
---|
163 | currently supported. If an AttributeSelector is found in the policy, |
---|
164 | this exception will be raised. |
---|
165 | """ |
---|
166 | if targetChild is None: |
---|
167 | # Default if target child is not set is to match all children |
---|
168 | return True |
---|
169 | |
---|
170 | for childMatch in targetChild.matches: |
---|
171 | # Get the match function from the Match ID |
---|
172 | matchFunc = self.matchFunc.get(childMatch.matchId) |
---|
173 | if matchFunc is NotImplemented: |
---|
174 | raise NotImplementedError('No match function implemented for ' |
---|
175 | 'MatchId="%s"' % childMatch.matchId) |
---|
176 | |
---|
177 | if matchFunc is None: |
---|
178 | raise Exception('Match function namespace %r is not recognised' |
---|
179 | % childMatch.matchId) |
---|
180 | |
---|
181 | matchAttributeValue = childMatch.attributeValue.value |
---|
182 | |
---|
183 | # Create a match function based on the presence or absence of an |
---|
184 | # AttributeDesignator or AttributeSelector |
---|
185 | if childMatch.attributeDesignator is not None: |
---|
186 | attributeId = childMatch.attributeDesignator.attributeId |
---|
187 | dataType = childMatch.attributeDesignator.dataType |
---|
188 | |
---|
189 | _attributeMatch = lambda requestChildAttribute: ( |
---|
190 | matchFunc(matchAttributeValue, |
---|
191 | requestChildAttribute.attributeValue.value) and |
---|
192 | requestChildAttribute.attributeId == attributeId and |
---|
193 | requestChildAttribute.dataType == dataType |
---|
194 | ) |
---|
195 | |
---|
196 | elif childMatch.attributeSelector is not None: |
---|
197 | # Nb. This will require that the request provide a reference to |
---|
198 | # it's XML representation and an abstraction of the XML parser |
---|
199 | # for executing XPath searches into that representation |
---|
200 | raise NotImplementedError('This PDP implementation does not ' |
---|
201 | 'support <AttributeSelector> ' |
---|
202 | 'elements') |
---|
203 | else: |
---|
204 | _attributeMatch = lambda requestChildAttribute: ( |
---|
205 | matchFunc(matchAttributeValue, |
---|
206 | requestChildAttribute.attributeValue.value) |
---|
207 | ) |
---|
208 | |
---|
209 | for attribute in requestChild.attributes: |
---|
210 | if _attributeMatch(attribute): |
---|
211 | return True |
---|
212 | |
---|
213 | return False |
---|
214 | |
---|