source: TI12-security/trunk/NDG_XACML/ndg/xacml/core/functions/__init__.py @ 7072

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg-security/TI12-security/trunk/NDG_XACML/ndg/xacml/core/functions/__init__.py@7072
Revision 7072, 27.8 KB checked in by pjkersha, 11 years ago (diff)

Incomplete - task 2: XACML-Security Integration

  • Major cleanup of function factories for efficiency. Only the required factories and function classes are loaded and any loaded classes are cached for future calls. All unit tests pass.
  • Property svn:keywords set to Id
Line 
1"""NDG XACML package for functions
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "26/03/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$"
12from abc import ABCMeta, abstractmethod
13from datetime import datetime, timedelta
14import traceback
15import logging
16log = logging.getLogger(__name__)
17
18from ndg.xacml.core.attributevalue import (AttributeValue, 
19                                           AttributeValueClassFactory)
20from ndg.xacml.utils import VettedDict, _isIterable
21from ndg.xacml.utils.factory import callModuleObject
22
23
24class AbstractFunction(object):
25    """Base class for all XACML matching functions"""
26   
27    __metaclass__ = ABCMeta
28    FUNCTION_NS = None
29    V1_0_FUNCTION_NS = "urn:oasis:names:tc:xacml:1.0:function:"
30    V2_0_FUNCTION_NS = "urn:oasis:names:tc:xacml:2.0:function:"
31   
32    def __init__(self):
33        if self.__class__.FUNCTION_NS is None:
34            raise TypeError('"FUNCTION_NS" class variable must be defined in '
35                            'derived classes')
36           
37    @abstractmethod
38    def evaluate(self, *inputs):
39        """Evaluate the function from the given input arguments and context
40        @param inputs: input arguments need to evaluate the function
41        @type inputs: tuple
42        @return: True for match, False otherwise
43        @rtype: bool
44        """
45       
46class XacmlFunctionNames(object):
47    """XACML standard match function names"""
48    FUNCTION_NAMES = (
49'urn:oasis:names:tc:xacml:1.0:function:string-equal',
50'urn:oasis:names:tc:xacml:1.0:function:boolean-equal',
51'urn:oasis:names:tc:xacml:1.0:function:integer-equal',
52'urn:oasis:names:tc:xacml:1.0:function:double-equal',
53'urn:oasis:names:tc:xacml:1.0:function:date-equal',
54'urn:oasis:names:tc:xacml:1.0:function:time-equal',
55'urn:oasis:names:tc:xacml:1.0:function:dateTime-equal',
56'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-equal',
57'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-equal',
58'urn:oasis:names:tc:xacml:1.0:function:anyURI-equal',
59'urn:oasis:names:tc:xacml:1.0:function:x500Name-equal',
60'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-equal',
61'urn:oasis:names:tc:xacml:1.0:function:hexBinary-equal',
62'urn:oasis:names:tc:xacml:1.0:function:base64Binary-equal',
63'urn:oasis:names:tc:xacml:1.0:function:integer-add',
64'urn:oasis:names:tc:xacml:1.0:function:double-add',
65'urn:oasis:names:tc:xacml:1.0:function:integer-subtract',
66'urn:oasis:names:tc:xacml:1.0:function:double-subtract',
67'urn:oasis:names:tc:xacml:1.0:function:integer-multiply',
68'urn:oasis:names:tc:xacml:1.0:function:double-multiply',
69'urn:oasis:names:tc:xacml:1.0:function:integer-divide',
70'urn:oasis:names:tc:xacml:1.0:function:double-divide',
71'urn:oasis:names:tc:xacml:1.0:function:integer-mod',
72'urn:oasis:names:tc:xacml:1.0:function:integer-abs',
73'urn:oasis:names:tc:xacml:1.0:function:double-abs',
74'urn:oasis:names:tc:xacml:1.0:function:round',
75'urn:oasis:names:tc:xacml:1.0:function:floor',
76'urn:oasis:names:tc:xacml:1.0:function:string-normalize-space',
77'urn:oasis:names:tc:xacml:1.0:function:string-normalize-to-lower-case',
78'urn:oasis:names:tc:xacml:1.0:function:double-to-integer',
79'urn:oasis:names:tc:xacml:1.0:function:integer-to-double',
80'urn:oasis:names:tc:xacml:1.0:function:or',
81'urn:oasis:names:tc:xacml:1.0:function:and',
82'urn:oasis:names:tc:xacml:1.0:function:n-of',
83'urn:oasis:names:tc:xacml:1.0:function:not',
84'urn:oasis:names:tc:xacml:1.0:function:integer-greater-than',
85'urn:oasis:names:tc:xacml:1.0:function:integer-greater-than-or-equal',
86'urn:oasis:names:tc:xacml:1.0:function:integer-less-than',
87'urn:oasis:names:tc:xacml:1.0:function:integer-less-than-or-equal',
88'urn:oasis:names:tc:xacml:1.0:function:double-greater-than',
89'urn:oasis:names:tc:xacml:1.0:function:double-greater-than-or-equal',
90'urn:oasis:names:tc:xacml:1.0:function:double-less-than',
91'urn:oasis:names:tc:xacml:1.0:function:double-less-than-or-equal',
92'urn:oasis:names:tc:xacml:1.0:function:dateTime-add-dayTimeDuration',
93'urn:oasis:names:tc:xacml:1.0:function:dateTime-add-yearMonthDuration',
94'urn:oasis:names:tc:xacml:1.0:function:dateTime-subtract-dayTimeDuration',
95'urn:oasis:names:tc:xacml:1.0:function:dateTime-subtract-yearMonthDuration', 
96'urn:oasis:names:tc:xacml:1.0:function:date-add-yearMonthDuration',
97'urn:oasis:names:tc:xacml:1.0:function:date-subtract-yearMonthDuration',
98'urn:oasis:names:tc:xacml:1.0:function:string-greater-than',
99'urn:oasis:names:tc:xacml:1.0:function:string-greater-than-or-equal',
100'urn:oasis:names:tc:xacml:1.0:function:string-less-than',
101'urn:oasis:names:tc:xacml:1.0:function:string-less-than-or-equal',
102'urn:oasis:names:tc:xacml:1.0:function:time-greater-than',
103'urn:oasis:names:tc:xacml:1.0:function:time-greater-than-or-equal',
104'urn:oasis:names:tc:xacml:1.0:function:time-less-than',
105'urn:oasis:names:tc:xacml:1.0:function:time-less-than-or-equal',
106'urn:oasis:names:tc:xacml:2.0:function:time-in-range',
107'urn:oasis:names:tc:xacml:1.0:function:dateTime-greater-than',
108'urn:oasis:names:tc:xacml:1.0:function:dateTime-greater-than-or-equal',
109'urn:oasis:names:tc:xacml:1.0:function:dateTime-less-than',
110'urn:oasis:names:tc:xacml:1.0:function:dateTime-less-than-or-equal',
111'urn:oasis:names:tc:xacml:1.0:function:date-greater-than',
112'urn:oasis:names:tc:xacml:1.0:function:date-greater-than-or-equal',
113'urn:oasis:names:tc:xacml:1.0:function:date-less-than',
114'urn:oasis:names:tc:xacml:1.0:function:date-less-than-or-equal',
115'urn:oasis:names:tc:xacml:1.0:function:string-one-and-only',
116'urn:oasis:names:tc:xacml:1.0:function:string-bag-size',
117'urn:oasis:names:tc:xacml:1.0:function:string-is-in',
118'urn:oasis:names:tc:xacml:1.0:function:string-bag',
119'urn:oasis:names:tc:xacml:1.0:function:boolean-one-and-only',
120'urn:oasis:names:tc:xacml:1.0:function:boolean-bag-size',
121'urn:oasis:names:tc:xacml:1.0:function:boolean-is-in',
122'urn:oasis:names:tc:xacml:1.0:function:boolean-bag',
123'urn:oasis:names:tc:xacml:1.0:function:integer-one-and-only',
124'urn:oasis:names:tc:xacml:1.0:function:integer-bag-size',
125'urn:oasis:names:tc:xacml:1.0:function:integer-is-in',
126'urn:oasis:names:tc:xacml:1.0:function:integer-bag',
127'urn:oasis:names:tc:xacml:1.0:function:double-one-and-only',
128'urn:oasis:names:tc:xacml:1.0:function:double-bag-size',
129'urn:oasis:names:tc:xacml:1.0:function:double-is-in',
130'urn:oasis:names:tc:xacml:1.0:function:double-bag',
131'urn:oasis:names:tc:xacml:1.0:function:time-one-and-only',
132'urn:oasis:names:tc:xacml:1.0:function:time-bag-size',
133'urn:oasis:names:tc:xacml:1.0:function:time-is-in',
134'urn:oasis:names:tc:xacml:1.0:function:time-bag',
135'urn:oasis:names:tc:xacml:1.0:function:date-one-and-only',
136'urn:oasis:names:tc:xacml:1.0:function:date-bag-size',
137'urn:oasis:names:tc:xacml:1.0:function:date-is-in',
138'urn:oasis:names:tc:xacml:1.0:function:date-bag',
139'urn:oasis:names:tc:xacml:1.0:function:dateTime-one-and-only',
140'urn:oasis:names:tc:xacml:1.0:function:dateTime-bag-size',
141'urn:oasis:names:tc:xacml:1.0:function:dateTime-is-in',
142'urn:oasis:names:tc:xacml:1.0:function:dateTime-bag',
143'urn:oasis:names:tc:xacml:1.0:function:anyURI-one-and-only',
144'urn:oasis:names:tc:xacml:1.0:function:anyURI-bag-size',
145'urn:oasis:names:tc:xacml:1.0:function:anyURI-is-in',
146'urn:oasis:names:tc:xacml:1.0:function:anyURI-bag',
147'urn:oasis:names:tc:xacml:1.0:function:hexBinary-one-and-only',
148'urn:oasis:names:tc:xacml:1.0:function:hexBinary-bag-size',
149'urn:oasis:names:tc:xacml:1.0:function:hexBinary-is-in',
150'urn:oasis:names:tc:xacml:1.0:function:hexBinary-bag',
151'urn:oasis:names:tc:xacml:1.0:function:base64Binary-one-and-only',
152'urn:oasis:names:tc:xacml:1.0:function:base64Binary-bag-size',
153'urn:oasis:names:tc:xacml:1.0:function:base64Binary-is-in',
154'urn:oasis:names:tc:xacml:1.0:function:base64Binary-bag',
155'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-one-and-only',
156'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-bag-size',
157'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-is-in',
158'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-bag',
159'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-one-and-only',
160'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-bag-size',
161'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-is-in',
162'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-bag',
163'urn:oasis:names:tc:xacml:1.0:function:x500Name-one-and-only',
164'urn:oasis:names:tc:xacml:1.0:function:x500Name-bag-size',
165'urn:oasis:names:tc:xacml:1.0:function:x500Name-is-in',
166'urn:oasis:names:tc:xacml:1.0:function:x500Name-bag',
167'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-one-and-only',
168'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-bag-size',
169'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-is-in',
170'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-bag',
171'urn:oasis:names:tc:xacml:2.0:function:string-concatenate',
172'urn:oasis:names:tc:xacml:2.0:function:uri-string-concatenate',
173'urn:oasis:names:tc:xacml:1.0:function:any-of',
174'urn:oasis:names:tc:xacml:1.0:function:all-of',
175'urn:oasis:names:tc:xacml:1.0:function:any-of-any',
176'urn:oasis:names:tc:xacml:1.0:function:all-of-any',
177'urn:oasis:names:tc:xacml:1.0:function:any-of-all',
178'urn:oasis:names:tc:xacml:1.0:function:all-of-all',
179'urn:oasis:names:tc:xacml:1.0:function:map',
180'urn:oasis:names:tc:xacml:1.0:function:x500Name-match',
181'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-match',
182'urn:oasis:names:tc:xacml:1.0:function:string-regexp-match',
183'urn:oasis:names:tc:xacml:2.0:function:anyURI-regexp-match',
184'urn:oasis:names:tc:xacml:2.0:function:ipAddress-regexp-match',
185'urn:oasis:names:tc:xacml:2.0:function:dnsName-regexp-match',
186'urn:oasis:names:tc:xacml:2.0:function:rfc822Name-regexp-match',
187'urn:oasis:names:tc:xacml:2.0:function:x500Name-regexp-match',
188'urn:oasis:names:tc:xacml:1.0:function:xpath-node-count',
189'urn:oasis:names:tc:xacml:1.0:function:xpath-node-equal',
190'urn:oasis:names:tc:xacml:1.0:function:xpath-node-match',
191'urn:oasis:names:tc:xacml:1.0:function:string-intersection',
192'urn:oasis:names:tc:xacml:1.0:function:string-at-least-one-member-of',
193'urn:oasis:names:tc:xacml:1.0:function:string-union',
194'urn:oasis:names:tc:xacml:1.0:function:string-subset',
195'urn:oasis:names:tc:xacml:1.0:function:string-set-equals',
196'urn:oasis:names:tc:xacml:1.0:function:boolean-intersection',
197'urn:oasis:names:tc:xacml:1.0:function:boolean-at-least-one-member-of',
198'urn:oasis:names:tc:xacml:1.0:function:boolean-union',
199'urn:oasis:names:tc:xacml:1.0:function:boolean-subset',
200'urn:oasis:names:tc:xacml:1.0:function:boolean-set-equals',
201'urn:oasis:names:tc:xacml:1.0:function:integer-intersection',
202'urn:oasis:names:tc:xacml:1.0:function:integer-at-least-one-member-of',
203'urn:oasis:names:tc:xacml:1.0:function:integer-union',
204'urn:oasis:names:tc:xacml:1.0:function:integer-subset',
205'urn:oasis:names:tc:xacml:1.0:function:integer-set-equals',
206'urn:oasis:names:tc:xacml:1.0:function:double-intersection',
207'urn:oasis:names:tc:xacml:1.0:function:double-at-least-one-member-of',
208'urn:oasis:names:tc:xacml:1.0:function:double-union',
209'urn:oasis:names:tc:xacml:1.0:function:double-subset',
210'urn:oasis:names:tc:xacml:1.0:function:double-set-equals',
211'urn:oasis:names:tc:xacml:1.0:function:time-intersection',
212'urn:oasis:names:tc:xacml:1.0:function:time-at-least-one-member-of',
213'urn:oasis:names:tc:xacml:1.0:function:time-union',
214'urn:oasis:names:tc:xacml:1.0:function:time-subset',
215'urn:oasis:names:tc:xacml:1.0:function:time-set-equals',
216'urn:oasis:names:tc:xacml:1.0:function:date-intersection',
217'urn:oasis:names:tc:xacml:1.0:function:date-at-least-one-member-of',
218'urn:oasis:names:tc:xacml:1.0:function:date-union',
219'urn:oasis:names:tc:xacml:1.0:function:date-subset',
220'urn:oasis:names:tc:xacml:1.0:function:date-set-equals',
221'urn:oasis:names:tc:xacml:1.0:function:dateTime-intersection',
222'urn:oasis:names:tc:xacml:1.0:function:dateTime-at-least-one-member-of',
223'urn:oasis:names:tc:xacml:1.0:function:dateTime-union',
224'urn:oasis:names:tc:xacml:1.0:function:dateTime-subset',
225'urn:oasis:names:tc:xacml:1.0:function:dateTime-set-equals',
226'urn:oasis:names:tc:xacml:1.0:function:anyURI-intersection',
227'urn:oasis:names:tc:xacml:1.0:function:anyURI-at-least-one-member-of',
228'urn:oasis:names:tc:xacml:1.0:function:anyURI-union',
229'urn:oasis:names:tc:xacml:1.0:function:anyURI-subset',
230'urn:oasis:names:tc:xacml:1.0:function:anyURI-set-equals',
231'urn:oasis:names:tc:xacml:1.0:function:hexBinary-intersection',
232'urn:oasis:names:tc:xacml:1.0:function:hexBinary-at-least-one-member-of',
233'urn:oasis:names:tc:xacml:1.0:function:hexBinary-union',
234'urn:oasis:names:tc:xacml:1.0:function:hexBinary-subset',
235'urn:oasis:names:tc:xacml:1.0:function:hexBinary-set-equals',
236'urn:oasis:names:tc:xacml:1.0:function:base64Binary-intersection',
237'urn:oasis:names:tc:xacml:1.0:function:base64Binary-at-least-one-member-of',
238'urn:oasis:names:tc:xacml:1.0:function:base64Binary-union',
239'urn:oasis:names:tc:xacml:1.0:function:base64Binary-subset',
240'urn:oasis:names:tc:xacml:1.0:function:base64Binary-set-equals',
241'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-intersection',
242'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-at-least-one-member-of',
243'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-union',
244'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-subset',
245'urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-set-equals',
246'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-intersection',
247'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-at-least-one-member-of',
248'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-union',
249'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-subset',
250'urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-set-equals',
251'urn:oasis:names:tc:xacml:1.0:function:x500Name-intersection',
252'urn:oasis:names:tc:xacml:1.0:function:x500Name-at-least-one-member-of',
253'urn:oasis:names:tc:xacml:1.0:function:x500Name-union',
254'urn:oasis:names:tc:xacml:1.0:function:x500Name-subset',
255'urn:oasis:names:tc:xacml:1.0:function:x500Name-set-equals',
256'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-intersection',
257'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-at-least-one-member-of',
258'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-union',
259'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-subset',
260'urn:oasis:names:tc:xacml:1.0:function:rfc822Name-set-equals',
261    )
262
263
264class FunctionClassFactoryInterface(object):
265    """Interface class for function module class factory class
266    """
267    __meta__ = ABCMeta
268   
269    @abstractmethod
270    def __call__(self, identifier):
271        '''Create class for the given XACML function identifier
272       
273        @param identifier: XACML function identifier
274        @type identifier: basestring
275        @return: at least one member of class corresponding to the given input
276        identifier
277        @rtype: AbstractFunction derived type or None if no match is
278        found
279        '''
280        return None
281   
282
283class FunctionClassFactoryBase(FunctionClassFactoryInterface):
284    """Base implementation for XACML Function Class Factory.  There should be
285    one derived type for each function family implemented in sub-modules of
286    ndg.xacml.core.functions
287   
288    e.g.
289   
290    for urn:oasis:names:tc:xacml:1.0:function:<type>-at-least-one-member-of a
291    class factory should exist,
292   
293    ndg.xacml.core.functions.v1.at_least_one_member_of.FunctionClassFactory
294   
295    which will be capable of returning a type derived from AbstractFunction:
296   
297    <type>AtLeastOneMemberOf   
298   
299    e.g. StringAtLeastOneMemberOf, BooleanAtLeastOneMemberOf.
300   
301    This class is for convenience only some function factories are better
302    derived directly from FunctionClassFactoryInterface
303   
304    Derived classes MUST define these class variables:
305   
306    @cvar FUNCTION_NAMES: list of function identifiers that this factory can
307    produce classes for e.g.:
308   
309    ('urn:oasis:names:tc:xacml:1.0:function:string-at-least-one-member-of', ...)
310   
311    @type FUNCTION_NAMES: NoneType (but list in derived class)
312   
313    @cvar FUNCTION_NS_SUFFIX: urn suffix for the family of function to define
314    e.g. -at-least-one-member-of is the suffix for the URN:
315   
316    urn:oasis:names:tc:xacml:1.0:function:string-at-least-one-member-of
317    @type FUNCTION_NS_SUFFIX: NoneType (but basestring in derived class)
318   
319    @cvar FUNCTION_BASE_CLASS: base class for this family of functions e.g for
320    urn:oasis:names:tc:xacml:1.0:function:string-at-least-one-member-of,
321    ndg.xacml.core.functions.v1.at_least_one_member_of.AtLeastOneMemberOfBase
322    @type FUNCTION_BASE_CLASS: NoneType (but AbstractFunction derived type in
323    derived function factory class)
324    """
325   
326    FUNCTION_NS_SUFFIX = None
327    FUNCTION_NAMES = None
328    FUNCTION_BASE_CLASS = None
329   
330    URN_SEP = ':'
331    FUNCTION_NAME_SEP = '-'
332    __slots__ = ('__map', 'attributeValueClassFactory', 'functionSuffix')
333   
334    def __init__(self):
335        '''This class is in fact abstract - derived types must define the
336        FUNCTION_NS_SUFFIX and FUNCTION_BASE_CLASS class variables
337        '''
338        if None in (self.__class__.FUNCTION_NS_SUFFIX, 
339                    self.__class__.FUNCTION_BASE_CLASS):
340            raise TypeError('"FUNCTION_NS_SUFFIX" and "FUNCTION_BASE_CLASS" '
341                            'must be defined in a derived implementation of '
342                            'FunctionClassFactoryBase.  See '
343                            'FunctionClassFactoryBase.__doc__ contents')
344       
345        if not _isIterable(self.__class__.FUNCTION_NAMES):
346            raise TypeError('"FUNCTION_NAMES" class variable must be an '
347                            'iterable of string type function identifiers; got '
348                            '%r' % self.__class__.FUNCTION_NAMES)
349
350        self.__map = {}   
351       
352        # Enables creation of matching attribute types to relevant to the
353        # function classes   
354        self.attributeValueClassFactory = AttributeValueClassFactory()
355           
356       
357        functionSuffixParts = self.__class__.FUNCTION_NS_SUFFIX.split(
358                                            self.__class__.FUNCTION_NAME_SEP)
359        self.functionSuffix = ''.join([n[0].upper() + n[1:] 
360                                  for n in functionSuffixParts if n])
361       
362    def initAllFunctionClasses(self):
363        """Create classes for all functions for a data type e.g. a derived class
364        could implement a factory for <type>-at-least-one-member-of functions:
365        string-at-least-one-member-of, boolean-at-least-one-member-of, etc.
366       
367        Function classes are placed in a look-up table __map for the __call__()
368        method to access
369       
370        In practice, there shouldn't be a need to load all the functions in
371        one go.  The __call__ method loads functions and caches them as needed.
372        """       
373        for identifier in self.__class__.FUNCTION_NAMES:
374            self.loadFunction(identifier)       
375
376    def loadFunction(self, identifier):
377        """Create a class for the given function namespace and cache it in the
378        function class look-up table for future requests.  Note that this call
379        overwrites any existing entry in the cache whereas __call__ will try
380        to use an entry in the cache if it already exists
381       
382        @param identifier: XACML function namespace
383        @type identifier: basestring
384        """
385
386        # str.capitalize doesn't do what's required: need to capitalize the
387        # first letter of the word BUT retain camel case for the rest of it
388        _capitalize = lambda s: s[0].upper() + s[1:]
389       
390        # Extract the function name and the type portion of the function
391        # name in order to make an implementation of a class to handle it
392        functionName = identifier.split(self.__class__.URN_SEP)[-1]
393        typePart = functionName.split(self.__class__.FUNCTION_NS_SUFFIX)[0]
394       
395        # Attempt to infer from the function name the associated type
396        typeName = _capitalize(typePart)
397       
398        # Remove any hyphens converting to camel case
399        if '-' in typeName:
400            typeName = ''.join([_capitalize(i) for i in typeName.split('-')])
401           
402        typeURI = AttributeValue.TYPE_URI_MAP.get(typeName)
403        if typeURI is None:
404            # Ugly hack to allow for XPath node functions
405            if typePart == 'xpath-node':
406                typeURI = AttributeValue.TYPE_URI_MAP['String']
407            else:
408                raise TypeError('No AttributeValue.TYPE_URI_MAP entry for '
409                                '%r type' % typePart) 
410           
411        _type = self.attributeValueClassFactory(typeURI)
412        if _type is None:
413            raise TypeError('No AttributeValue.TYPE_MAP entry for %r type' %
414                            typeName)
415         
416        className = typeName + self.functionSuffix
417        classVars = {
418            'TYPE': _type,
419            'FUNCTION_NS': identifier
420        }
421       
422        functionClass = type(className, 
423                             (self.__class__.FUNCTION_BASE_CLASS, ), 
424                             classVars)
425       
426        self.__map[identifier] = functionClass
427           
428    def __call__(self, identifier):
429        """Return the class for the given XACML *-at-least-one-member-of type
430        function identifier
431        @param identifier: XACML *-at-least-one-member-of type function
432        identifier
433        @type identifier: basestring
434        @return: at least one member of class corresponding to the given input
435        identifier
436        @rtype: AtLeastOneMemberOfBase derived type or None if no match is
437        found
438        """
439        # Check the cache first
440        functionClass = self.__map.get(identifier)
441        if functionClass is None:
442            # No class set in the cache - try loading the new class and updating
443            # the cache.
444            self.loadFunction(identifier)
445           
446        # This should result in a safe retrieval from the cache because of the
447        # above check - None return would result otherwise.
448        return self.__map.get(identifier)
449       
450   
451class FunctionMapError(Exception):
452    """Generic Error exception class for FunctionMap"""
453   
454   
455class FunctionMapConfigError(FunctionMapError):
456    """Configuration related exception for FunctionMap"""
457       
458       
459class FunctionMap(VettedDict):
460    """Map function IDs to their class implementations in the various function
461    sub-modules.  It provide a layer over the various
462    FunctionClassFactoryInterface implementations so that a function class can
463    be obtained directly from a given XACML function URN. 
464    """
465    FUNCTION_PKG_PREFIX = 'ndg.xacml.core.functions.'
466   
467    V1_0_PKG_PREFIX = FUNCTION_PKG_PREFIX + 'v1.'
468    V2_0_PKG_PREFIX = FUNCTION_PKG_PREFIX + 'v2.'
469   
470    SUPPORTED_NSS = {
471        AbstractFunction.V1_0_FUNCTION_NS: V1_0_PKG_PREFIX,
472        AbstractFunction.V2_0_FUNCTION_NS: V2_0_PKG_PREFIX
473    }
474   
475    # Each function module is expected to have a class factory for obtaining
476    # a class for the given function identifier associated with that module
477    FUNCTION_CLASS_FACTORY_CLASSNAME = 'FunctionClassFactory'
478   
479    def __init__(self):
480        """Force type for dictionary key value pairs: function values must be
481        of AbstractFunction derived type and ID keys string type
482        """       
483        # Filters are defined as staticmethods but reference via self here to
484        # enable derived class to override them as standard methods without
485        # needing to redefine this __init__ method           
486        VettedDict.__init__(self, self.keyFilter, self.valueFilter)
487       
488        # This classes maintains a list of XACML function URN -> Function class
489        # mappings.  This additional dict enables caching of class factories
490        # used to obtain the function classes.  There is one class factory per
491        # function module e.g. ndg.xacml.core.functions.v1.equal contains a
492        # class factory which creates the various
493        # urn:oasis:names:tc:xacml:1.0:function:<type>-equal function classes
494        self.__classFactoryMap = {}
495       
496    @staticmethod
497    def keyFilter(key):
498        """Enforce string type keys"""
499        if not isinstance(key, basestring):
500            raise TypeError('Expecting %r type for key; got %r' % 
501                            (basestring, type(key))) 
502           
503        return True 
504   
505    @staticmethod
506    def valueFilter(value):
507        """Enforce AbstractFunction derived types for match functions"""
508        if value is NotImplemented:
509            return True
510       
511        elif not issubclass(value, AbstractFunction):
512            raise TypeError('Expecting %r derived type for value; got %r' % 
513                            (AbstractFunction, value)) 
514           
515        return True 
516           
517    def loadAll(self):
518        """Load function map with implementations from the relevant function
519        package"""
520       
521        for functionNs in XacmlFunctionNames.FUNCTION_NAMES:
522            self.loadFunction(functionNs)
523           
524    def loadFunction(self, functionNs):
525        """Get package to retrieve function class for the given XACML function
526        namespace
527       
528        @param functionNs: XACML function namespace
529        @type functionNs: basestring
530        """
531        functionFactory = self.__classFactoryMap.get(functionNs)
532        if functionFactory is not None:
533            # Get function class from previously cached factory
534            self[functionNs] = functionFactory(functionNs)
535            return
536           
537        # No Factory has been cached for this function yet
538        cls = FunctionMap
539        classPath = None
540       
541        for namespacePrefix, pkgNamePrefix in cls.SUPPORTED_NSS.items():
542            if functionNs.startswith(namespacePrefix):
543                # Namespace is recognised - translate into a path to a
544                # function class in the right functions package
545                functionName = functionNs.split(namespacePrefix)[-1]
546                functionNameParts = functionName.split('-')
547               
548                if len(functionNameParts) == 1:
549                    moduleName = functionNameParts[0]
550                   
551                elif functionName.startswith('xpath-node'):
552                    # Ugly hack for xpath-node functions
553                    moduleName = functionNameParts[-1].lower()
554                else:
555                    moduleName = '_'.join(functionNameParts[1:]).lower()
556                   
557                classPath = pkgNamePrefix + moduleName + '.' + \
558                            cls.FUNCTION_CLASS_FACTORY_CLASSNAME
559                break
560
561        if classPath is None:
562            raise FunctionMapConfigError('Namespace for function not '
563                                         'recognised: %r' % functionNs) 
564                       
565        # Try instantiating the function class and loading it into the map
566        try:
567            functionFactory = callModuleObject(classPath)
568                     
569        except (ImportError, AttributeError), e:
570            log.error("Error importing function factory class %r for function "
571                      "identifier %r: %s", classPath, functionNs, str(e))
572           
573            # No implementation exists - default to Abstract function
574            self[functionNs] = NotImplemented
575        else:
576            self[functionNs] = functionFactory(functionNs)
577            self.__classFactoryMap[functionNs] = functionFactory
578                       
579    def __getitem__(self, key):
580        """Override base class implementation to load and cache function classes
581        if they don't otherwise exist
582        """
583        functionClass = VettedDict.get(self, key)
584        if functionClass is None:
585            self.loadFunction(key)
586           
587        return VettedDict.__getitem__(self, key)
588       
589    def get(self, key, *arg):
590        """Likewise to __getitem__, enable loading and caching of function
591        classes if they don't otherwise exist
592        """
593        functionClass = VettedDict.get(self, key, *arg)
594        if functionClass is None:
595            self.loadFunction(key)
596            return VettedDict.get(self, key, *arg)   
597        else:
598            return functionClass
599
600
601       
Note: See TracBrowser for help on using the repository browser.