source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/wssecurity/__init__.py @ 5359

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