source: TI12-security/trunk/ndg_xacml/ndg/xacml/core/functions/__init__.py @ 7668

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

Modified apply and match classes to use a FunctionMap? class singleton to enable easy addition of new function types.

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