source: TI12-security/trunk/WSSecurity/ndg/soap/wssecurity/common/__init__.py @ 6392

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/WSSecurity/ndg/soap/wssecurity/common/__init__.py@6413
Revision 6392, 10.7 KB checked in by pjkersha, 10 years ago (diff)
Line 
1"""NDG Security WS-Security package - contains signature handler and config
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "01/04/08"
7__copyright__ = "(C) 2009 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: $"
12import logging
13log = logging.getLogger(__name__)
14
15import os
16
17from ConfigParser import SafeConfigParser
18from os.path import expandvars as exVar
19import copy
20from ZSI.wstools.Namespaces import OASIS
21
22from ndg.security.common.utils.configfileparsers import \
23    CaseSensitiveConfigParser
24
25class WSSecurityError(Exception):
26    """For WS-Security generic exceptions not covered by other exception
27    classes in this module"""
28    def __init__(self, errorMessage):
29        log.error(errorMessage)
30        super(WSSecurityError, self).__init__(errorMessage)
31       
32       
33class WSSecurityConfigError(WSSecurityError):
34    """Configuration error with WS-Security setting or settings"""
35   
36   
37class WSSecurityConfigOpNotPermitted(WSSecurityConfigError):
38    "Raise for dict methods not allowed in WSSecurityConfig"
39
40
41class InvalidCertChain(WSSecurityError):   
42    """Raised from SignatureHandler.verify if the certificate submitted to
43    verify a signature is not from a known CA"""
44
45 
46class TimestampError(WSSecurityError):
47    """Raised from SignatureHandler._verifyTimestamp if there is a problem with
48    the created or expiry times in an input message Timestamp"""
49
50
51class MessageExpired(TimestampError):
52    """Raised from SignatureHandler._verifyTimestamp if the timestamp of
53    the message being processed is before the current time.  Can be caught in
54    order to set a wsu:MessageExpired fault code"""   
55class WSSecurityConfig(dict):
56    """Parser for WS-Security configuration.  Extends dict to enable
57    convenient interface for access to params.
58    """
59    propertyDefaults = dict(
60        reqBINARY_SECURITY_TOK_VAL_TYPE=OASIS.X509TOKEN.X509,
61        verifyingCert=None,
62        verifyingCertFilePath=None,
63        signingCert=None,
64        signingCertFilePath=None, 
65        signingCertChain=[],
66        signingPriKey=None,
67        signingPriKeyFilePath=None, 
68        signingPriKeyPwd=None,
69        caCertDirPath=None,
70        caCertFilePathList=[],
71        addTimestamp=True,
72        timestampClockSkew=0.,
73        timestampMustBeSet=False,
74        createdElemMustBeSet=True,
75        expiresElemMustBeSet=True,
76        applySignatureConfirmation=False,
77        refC14nInclNS=[],
78        signedInfoC14nInclNS=[])
79   
80    def __init__(self, cfg=SafeConfigParser()):
81        '''Initialise settings from an existing config file object or the
82        given path to config file
83       
84        @type cfg: SafeConfigParser or string
85        @param cfg: config object instance or file path to config file to be
86        parsed'''
87       
88        dict.__init__(self)
89       
90        # Initialise parameters from ref in class var
91        self._param = WSSecurityConfig.propertyDefaults.copy()
92       
93        if isinstance(cfg, basestring):
94            # Assume file path to be read
95            self.read(cfg)
96        else:
97            # Assume existing config type object
98            self._cfg = cfg
99       
100
101    def read(self, filePath):
102        '''Read ConfigParser object
103       
104        @type filePath: basestring
105        @param filePath: file to read config from'''
106       
107        # Expand environment variables in file path
108        expandedFilePath = exVar(filePath)
109       
110        # Add 'here' item to enable convenient path substitutions in the config
111        # file
112        defaultItems = dict(here=os.path.dirname(expandedFilePath))
113        self._cfg = CaseSensitiveConfigParser(defaults=defaultItems)
114       
115        readFilePaths = self._cfg.read(expandedFilePath)
116       
117        # Check file was read in OK
118        if len(readFilePaths) == 0:
119            raise IOError('Missing config file: "%s"' % expandedFilePath)
120
121    def parse(self, **kw):
122        '''Extract items from config file and place in dict
123        @type **kw: dict
124        @param **kw: this enables WS-Security params to be set in a config file
125        with other sections e.g. params could be under the section 'wssecurity'
126        '''
127        section = kw.pop('section', 'DEFAULT')
128       
129        # Prefix for option names - optNames = name as they appear in the
130        # config file, self._param are the names used in the code.
131        prefix = kw.pop('prefix', None)
132
133        if prefix:
134            optNames = ["%s.%s" % (prefix, optName) for optName in self._param] 
135        else:
136            optNames = self._param
137           
138        for optName, paramName in zip(optNames, self._param):
139           
140            # Parameters may be omitted and set later
141            if self._cfg.has_option(section, optName):
142                if isinstance(WSSecurityConfig.propertyDefaults[paramName], 
143                              list):
144                    try:
145                        self._param[paramName] = \
146                            exVar(self._cfg.get(section, optName)).split()
147                    except AttributeError:
148                        raise WSSecurityConfigError('Setting "%s"' % paramName)
149                   
150                elif isinstance(WSSecurityConfig.propertyDefaults[paramName], 
151                                bool):           
152                    self._param[paramName] = self._cfg.getboolean(section, 
153                                                                  optName)
154                else:
155                    # Default to None if setting is an empty string.  Settings
156                    # of '' causes problems for M2Crypto parsing
157                    self._param[paramName] = \
158                        exVar(self._cfg.get(section, optName)) or None
159
160    def __len__(self):
161        return len(self._param)
162   
163    def __iter__(self):
164        return self._param.__iter__()
165   
166    def __repr__(self):
167        """Return file properties dictionary as representation"""
168        return repr(self._param)
169
170    def __delitem__(self, key):
171        "Session Manager keys cannot be removed"       
172        raise KeyError('Keys cannot be deleted from ' + \
173                        WSSecurityConfig.__name__)
174
175    def __getitem__(self, key):
176        WSSecurityConfig.__name__ + \
177        """ behaves as data dictionary of WS-Security properties
178        """
179        if key not in WSSecurityConfig.propertyDefaults:
180            raise KeyError("Invalid key '%s'" % key)
181       
182        return self._param[key] 
183   
184    def __setitem__(self, key, item):
185        WSSecurityConfig.__name__ + \
186        """ behaves as data dictionary of WS-Security properties"""
187        if key not in WSSecurityConfig.propertyDefaults:
188            raise KeyError("Parameter key '%s' is not recognised" % key)
189       
190        self._param[key] = item
191
192    def copy(self):
193        wsSecurityConfig = WSSecurityConfig()
194        wsSecurityConfig._param = self._param.copy()
195        return wsSecurityConfig
196   
197    def get(self, key, *arg):
198        return self._param.get(key, *arg)
199
200    def clear(self):
201        raise WSSecurityConfigOpNotPermitted("Data cannot be cleared from "+\
202                                             WSSecurityConfig.__name__)
203   
204    def keys(self):
205        return self._param.keys()
206
207    def items(self):
208        return self._param.items()
209
210    def values(self):
211        return self._param.values()
212
213    def has_key(self, key):
214        return self._param.has_key(key)
215
216    # 'in' operator
217    def __contains__(self, key):
218        return key in self._param
219   
220    def update(self, seq, *arg, **kw):
221
222        # Prefix for option names - optNames = name as they appear in the
223        # config file, self._param are the names used in the code.
224        prefix = kw.pop('prefix', None)
225        if prefix:
226            pfxWithDot = prefix+'.'
227            seqFilt = dict([(k.replace(pfxWithDot, ''), v) 
228                            for k, v in seq.items() 
229                            if k.startswith(pfxWithDot)])
230        else:
231            seqFilt = seq
232       
233        badKeys = []
234        for optName, optVal in seqFilt.items():
235            if optName not in WSSecurityConfig.propertyDefaults:
236                badKeys += [optName]
237               
238            elif isinstance(WSSecurityConfig.propertyDefaults[optName], list):
239                if isinstance(optVal, basestring):
240                    # Parse into a list
241                    seqFilt[optName] = exVar(optVal).split()
242                elif isinstance(optVal, list):
243                    seqFilt[optName] = exVar(optVal)
244                else:
245                    raise WSSecurityConfigError("Expecting list type for "
246                                                'option "%s"' % optName)
247            elif isinstance(WSSecurityConfig.propertyDefaults[optName], bool):
248                if isinstance(optVal, basestring):
249                    # Parse into a boolean
250                    seqFilt[optName] = bool(optVal)
251                   
252                elif isinstance(optVal, bool):
253                    seqFilt[optName] = optVal
254                else:
255                    raise WSSecurityConfigError("Expecting bool type for "
256                                                'option "%s"' % optName)
257            else:
258                # Default to None if setting is an empty string.  Settings
259                # of '' causes problems for M2Crypto parsing
260                if optVal is None:
261                    seqFilt[optName] = optVal
262                else:
263                    seqFilt[optName] = exVar(optVal) or None
264               
265        if len(badKeys) > 0:
266            log.warning("Ignoring unrecognised parameter key(s) for update: "
267                        "%s" % ', '.join(badKeys))
268
269        return self._param.update(seqFilt, *arg)
270   
271    def fromkeys(self, seq):
272        badKeys=[i for i in seq if i not in WSSecurityConfig.propertyDefaults]
273        if badKeys:
274            raise KeyError("Parameter key(s) %s not recognised" % 
275                           ','.join(badKeys))
276        return self._param.fromkeys(seq)
277   
278    def setdefault(self, key, *arg):
279        badKeys=[i for i in arg if i not in WSSecurityConfig.propertyDefaults]
280        if badKeys:
281            raise KeyError("Parameter keys '%s' not recognised" % badKeys)
282        return self._param.setdefault(key, *arg)
283
284    def pop(self, key, *arg):
285        raise WSSecurityConfigOpNotPermitted("Params should not be deleted")
286   
287    def popitem(self):
288        raise WSSecurityConfigOpNotPermitted("Params should not be deleted")
289   
290    def iteritems(self):
291        return self._param.iteritems()
292   
293    def iterkeys(self):
294        return self._param.iterkeys()
295   
296    def itervalues(self):
297        return self._param.itervalues()
298   
Note: See TracBrowser for help on using the repository browser.