Changeset 7663 for TI12-security


Ignore:
Timestamp:
28/10/10 13:25:38 (9 years ago)
Author:
pjkersha
Message:

Added SOAPFault handling for ndg.soap package. Keeping this work sync with ndg.soap in the ndg_saml egg.

Location:
TI12-security/trunk/ndg_soap
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/ndg_soap/ndg/soap/__init__.py

    r7134 r7663  
    1515import logging 
    1616log = logging.getLogger(__name__) 
    17  
    18 class SOAPException(Exception): 
    19     """Base SAOP Exception class""" 
    20      
    21 class SOAPFault(SOAPException): 
    22     """SOAP Fault""" 
    23      
     17     
     18      
    2419class SOAPObject(object): 
    2520    """Base class for SOAP envelope, header and body elements""" 
     
    3025    DEFAULT_NS = SOAP11_NS 
    3126     
     27    __slots__ = () 
     28     
    3229    def create(self): 
    3330        raise NotImplementedError() 
    3431     
    35     def parse(self): 
     32    def parse(self, source): 
    3633        raise NotImplementedError() 
    3734     
     
    4138    def prettyPrint(self): 
    4239        raise NotImplementedError() 
    43    
     40       
    4441     
    4542class SOAPEnvelopeBase(SOAPObject): 
     
    5350    soapBody = property() 
    5451     
     52    __slots__ = () 
     53     
    5554     
    5655class SOAPHeaderBase(SOAPObject): 
     
    6059    DEFAULT_ELEMENT_NS = SOAPObject.DEFAULT_NS 
    6160    DEFAULT_ELEMENT_NS_PREFIX = SOAPObject.ELEMENT_PREFIX     
     61    __slots__ = () 
     62        
    6263         
    6364class SOAPBodyBase(SOAPObject): 
     
    6768    DEFAULT_ELEMENT_NS = SOAPObject.DEFAULT_NS 
    6869    DEFAULT_ELEMENT_NS_PREFIX = SOAPObject.ELEMENT_PREFIX 
     70    fault = property() 
     71    __slots__ = () 
     72  
     73         
     74class SOAPFaultBase(SOAPObject): 
     75    """SOAP Fault""" 
     76     
     77    DEFAULT_ELEMENT_LOCAL_NAME = "Fault" 
     78    DEFAULT_ELEMENT_NS = SOAPObject.DEFAULT_NS 
     79    DEFAULT_ELEMENT_NS_PREFIX = SOAPObject.ELEMENT_PREFIX 
     80     
     81    FAULT_CODE_ELEMENT_LOCAL_NAME = "faultcode" 
     82    FAULT_STRING_ELEMENT_LOCAL_NAME = "faultstring" 
     83    FAULT_ACTOR_ELEMENT_LOCAL_NAME = "faultactor" 
     84    DETAIL_ELEMENT_LOCAL_NAME = "detail" 
     85     
     86    VERSION_MISMATCH_CODE = "VersionMismatch" 
     87    MUST_UNDERSTAND_FAULT_CODE = "MustUnderstand" 
     88    CLIENT_FAULT_CODE = "Client" 
     89    SERVER_FAULT_CODE = "Server" 
     90     
     91    FAULT_CODES = ( 
     92        VERSION_MISMATCH_CODE,  
     93        MUST_UNDERSTAND_FAULT_CODE,  
     94        CLIENT_FAULT_CODE,  
     95        SERVER_FAULT_CODE 
     96    ) 
     97     
     98    __slots__ = ("__faultCode", "__faultString", "__faultActor", "__detail") 
     99     
     100    def __init__(self, faultString=None, faultCode=None, faultActor=None,  
     101                 detail=None): 
     102        """Initialise attributes""" 
     103        super(SOAPFaultBase, self).__init__() 
     104         
     105        if faultCode is None: 
     106            self.__faultCode = None 
     107        else: 
     108            self.faultCode = faultCode 
     109             
     110        if faultString is None: 
     111            self.__faultString = None 
     112        else: 
     113            self.faultString = faultString 
     114             
     115        if faultActor is None: 
     116            self.__faultActor = None 
     117        else: 
     118            self.faultActor = faultActor 
     119         
     120        if detail is None: 
     121            self.__detail = None 
     122        else: 
     123            self.detail = detail 
     124 
     125    def _setFaultCode(self, value): 
     126        if not isinstance(value, basestring): 
     127            raise AttributeError('Expecting string type for "faultCode" ' 
     128                                 'attribute; got %r' % type(value)) 
     129             
     130        qnameElems = value.split(':') 
     131        if len(qnameElems) == 0: 
     132            raise AttributeError('Expecting Qualified Name for "faultCode" ' 
     133                                 'attribute; got %r' % value) 
     134         
     135        faultCodeFound = [qnameElems[1].startswith(i)  
     136                          for i in self.__class__.FAULT_CODES] 
     137        if max(faultCodeFound) == False: 
     138            raise AttributeError('Expecting "faultCode" prefixed with one of ' 
     139                                 '%r; got %r' % (self.__class__.FAULT_CODES, 
     140                                                 value)) 
     141             
     142        self.__faultCode = value 
     143         
     144    def _getFaultCode(self): 
     145        return self.__faultCode 
     146 
     147    faultCode = property(_getFaultCode, _setFaultCode,  
     148                         doc="Fault Code") 
     149 
     150    def _setFaultString(self, value): 
     151        if not isinstance(value, basestring): 
     152            raise AttributeError('Expecting string type for "faultString" ' 
     153                                 'attribute; got %r' % type(value)) 
     154        self.__faultString = value 
     155         
     156    def _getFaultString(self): 
     157        return self.__faultString 
     158 
     159    faultString = property(_getFaultString, _setFaultString,  
     160                           doc="Fault String") 
     161 
     162    def _getFaultActor(self): 
     163        return self.__faultActor 
     164 
     165    def _setFaultActor(self, value): 
     166        if not isinstance(value, basestring): 
     167            raise AttributeError('Expecting string type for "faultActor" ' 
     168                                 'attribute; got %r' % type(value)) 
     169        self.__faultActor = value 
     170 
     171    faultActor = property(_getFaultActor, _setFaultActor,  
     172                          doc="Fault Actor") 
     173 
     174    def _getDetail(self): 
     175        return self.__detail 
     176 
     177    def _setDetail(self, value): 
     178        """No type checking - detail could be an XML element or serialised  
     179        string content""" 
     180        self.__detail = value 
     181 
     182    detail = property(_getDetail, _setDetail, doc="Fault detail") 
     183 
     184 
     185class SOAPException(Exception): 
     186    """Base SOAP Exception class""" 
     187         
     188     
     189class SOAPFaultException(Exception): 
     190    """Raise an exception which also creates a fault object""" 
     191    SOAP_FAULT_CLASS = SOAPFaultBase 
     192     
     193    def __init__(self, faultString, faultCode, faultActor=None, detail=None): 
     194        super(SOAPFaultException, self).__init__(faultString) 
     195        self.__fault = self.__class__.SOAP_FAULT_CLASS(faultString, faultCode,  
     196                                                       faultActor=faultActor,  
     197                                                       detail=detail) 
     198         
     199    @property     
     200    def fault(self): 
     201        """Get SOAP fault object""" 
     202        return self.__fault 
  • TI12-security/trunk/ndg_soap/ndg/soap/etree.py

    r7131 r7663  
    2222 
    2323from ndg.soap import (SOAPObject, SOAPEnvelopeBase, SOAPHeaderBase,  
    24                       SOAPBodyBase, SOAPFault) 
     24                      SOAPBodyBase, SOAPFaultBase) 
     25from ndg.soap import SOAPFaultException as SOAPFaultExceptionBase 
    2526 
    2627 
     
    6869        return xml 
    6970 
    70     def _parse(self, source): 
     71    @staticmethod 
     72    def _parse(source): 
    7173        """Read in the XML from source 
    7274        @type source: basestring/file 
     
    124126                           tag=SOAPBodyBase.DEFAULT_ELEMENT_LOCAL_NAME,  
    125127                           prefix=SOAPBodyBase.DEFAULT_ELEMENT_NS_PREFIX) 
     128        self.__fault = None 
     129         
     130    # Test for SOAPFault present 
     131    @property 
     132    def hasSOAPFault(self): 
     133        """Boolean True if this SOAP BOdy contains a SOAPFault instance""" 
     134        return self.fault is not None 
     135     
     136    def _getFault(self): 
     137        return self.__fault 
     138     
     139    def _setFault(self, value): 
     140        if not isinstance(value, SOAPFault): 
     141            raise TypeError('Expecting %r type for "fault" attribute; got %r' % 
     142                            (SOAPFault, type(value))) 
     143        self.__fault = value 
     144        
     145    fault = property(_getFault, _setFault, doc="SOAP Fault") 
    126146         
    127147    def create(self): 
    128148        """Create header ElementTree element""" 
    129149        self.elem = ElementTree.Element(str(self.qname)) 
     150        if self.hasSOAPFault: 
     151            self.fault.create() 
     152            self.elem.append(self.fault.elem) 
    130153     
    131154    def serialize(self): 
    132155        """Serialise element tree into string""" 
    133156        return ETreeSOAPExtensions._serialize(self.elem) 
    134      
     157  
     158    def parse(self, source): 
     159        """This method ONLY parses a SOAPFault IF one is found""" 
     160        if ElementTree.iselement(source): 
     161            self.elem = source 
     162        else: 
     163            self.elem = self._parse(source) 
     164                   
     165        for elem in self.elem: 
     166            localName = QName.getLocalPart(elem.tag) 
     167            if localName == SOAPFault.DEFAULT_ELEMENT_LOCAL_NAME: 
     168                if self.fault is None: 
     169                    self.fault = SOAPFault() 
     170                     
     171                self.fault.parse(elem) 
     172                 
     173                # Only one SOAPFault element is expected 
     174                break 
     175             
    135176    def prettyPrint(self): 
    136177        """Basic pretty printing separating each element on to a new line""" 
     
    138179     
    139180 
     181class SOAPFault(SOAPFaultBase, ETreeSOAPExtensions): 
     182    """Extend SOAP Fault for ElementTree parsing and serialisation""" 
     183     
     184    DEFAULT_ELEMENT_NAME = QName(SOAPFaultBase.DEFAULT_ELEMENT_NS, 
     185                                 tag=SOAPFaultBase.DEFAULT_ELEMENT_LOCAL_NAME, 
     186                                 prefix=SOAPFaultBase.DEFAULT_ELEMENT_NS_PREFIX) 
     187     
     188    FAULT_CODE_ELEMENT_NAME = QName(SOAPFaultBase.DEFAULT_ELEMENT_NS, 
     189                             tag=SOAPFaultBase.FAULT_CODE_ELEMENT_LOCAL_NAME, 
     190                             prefix=SOAPFaultBase.DEFAULT_ELEMENT_NS_PREFIX) 
     191     
     192    FAULT_STRING_ELEMENT_NAME = QName(SOAPFaultBase.DEFAULT_ELEMENT_NS, 
     193                             tag=SOAPFaultBase.FAULT_STRING_ELEMENT_LOCAL_NAME, 
     194                             prefix=SOAPFaultBase.DEFAULT_ELEMENT_NS_PREFIX) 
     195     
     196    FAULT_ACTOR_ELEMENT_NAME = QName(SOAPFaultBase.DEFAULT_ELEMENT_NS, 
     197                             tag=SOAPFaultBase.FAULT_ACTOR_ELEMENT_LOCAL_NAME, 
     198                             prefix=SOAPFaultBase.DEFAULT_ELEMENT_NS_PREFIX) 
     199     
     200    DETAIL_ELEMENT_NAME = QName(SOAPFaultBase.DEFAULT_ELEMENT_NS, 
     201                                tag=SOAPFaultBase.DETAIL_ELEMENT_LOCAL_NAME, 
     202                                prefix=SOAPFaultBase.DEFAULT_ELEMENT_NS_PREFIX) 
     203     
     204    def __init__(self, *arg, **kw): 
     205        SOAPFaultBase.__init__(self, *arg, **kw) 
     206        ETreeSOAPExtensions.__init__(self) 
     207         
     208        self.qname = QName(SOAPFaultBase.DEFAULT_ELEMENT_NS,  
     209                           tag=SOAPFaultBase.DEFAULT_ELEMENT_LOCAL_NAME,  
     210                           prefix=SOAPFaultBase.DEFAULT_ELEMENT_NS_PREFIX) 
     211     
     212    def _setFaultCode(self, value): 
     213        """Override to enable ns prefix to be inferred if not added in already 
     214        """ 
     215        if value.startswith(self.__class__.FAULT_CODE_ELEMENT_NAME.prefix): 
     216            _value = value 
     217        else: 
     218            _value = "%s:%s" % (SOAPFaultBase.DEFAULT_ELEMENT_NS_PREFIX, value) 
     219         
     220        SOAPFaultBase._setFaultCode(self, _value) 
     221         
     222    faultCode = property(SOAPFaultBase._getFaultCode, _setFaultCode, 
     223                         doc="Fault code") 
     224     
     225    def create(self): 
     226        """Create Fault ElementTree element""" 
     227        self.elem = ElementTree.Element(str(self.qname)) 
     228         
     229        faultStringElem = ElementTree.Element( 
     230                                str(self.__class__.FAULT_STRING_ELEMENT_NAME)) 
     231        faultStringElem.text = self.faultString 
     232        self.elem.append(faultStringElem) 
     233         
     234        faultCodeElem = ElementTree.Element( 
     235                                str(self.__class__.FAULT_CODE_ELEMENT_NAME)) 
     236        faultCodeElem.text = self.faultCode 
     237        self.elem.append(faultCodeElem) 
     238                          
     239        if self.faultActor is not None: 
     240            faultActorElem = ElementTree.Element( 
     241                                str(self.__class__.FAULT_ACTOR_ELEMENT_NAME)) 
     242            faultActorElem.text = self.faultActor 
     243            self.elem.append(faultActorElem) 
     244                              
     245        if self.detail is not None: 
     246            detailElem = ElementTree.Element( 
     247                                str(self.__class__.DETAIL_ELEMENT_NAME)) 
     248             
     249            if ElementTree.iselement(self.detail): 
     250                detailElem.append(self.detail) 
     251                 
     252            elif isinstance(self.detail, basestring):  
     253                detailElem.text = self.detail 
     254            else: 
     255                raise TypeError('Expecting ElementTree.Element or string ' 
     256                                'type for SOAPFault detail; got %r' % 
     257                                type(self.detail)) 
     258                 
     259            self.elem.append(detailElem) 
     260     
     261    def parse(self, source): 
     262        """Parse SOAPFault element""" 
     263        if ElementTree.iselement(source): 
     264            self.elem = source 
     265        else: 
     266            self.elem = self._parse(source) 
     267                   
     268        for elem in self.elem: 
     269            localName = QName.getLocalPart(elem.tag) 
     270            if localName == SOAPFault.FAULT_CODE_ELEMENT_LOCAL_NAME: 
     271                self.faultCode = elem.text.strip()  
     272                 
     273            elif localName == SOAPFault.FAULT_STRING_ELEMENT_LOCAL_NAME: 
     274                self.faultString = elem.text.strip() 
     275             
     276            elif localName == SOAPFault.FAULT_ACTOR_ELEMENT_LOCAL_NAME: 
     277                self.faultActor = elem.text.strip()     
     278             
     279            elif localName == SOAPFault.DETAIL_ELEMENT_LOCAL_NAME: 
     280                # Make no assumptions about the content, simply assing the  
     281                # element to the detail attribute 
     282                self.detail = elem    
     283                  
     284            else: 
     285                faultCode = str(QName(SOAPFault.DEFAULT_ELEMENT_NS,  
     286                                  tag=SOAPFault.CLIENT_FAULT_CODE,  
     287                                  prefix=SOAPFault.DEFAULT_ELEMENT_NS_PREFIX)) 
     288                 
     289                raise SOAPFaultException('Invalid child element in SOAP ' 
     290                                         'Fault "%s" for stream %r' %  
     291                                         (localName, source), 
     292                                         faultCode) 
     293             
     294    def serialize(self): 
     295        """Serialise element tree into string""" 
     296        return ETreeSOAPExtensions._serialize(self.elem) 
     297     
     298    def prettyPrint(self): 
     299        """Basic pretty printing separating each element on to a new line""" 
     300        return ETreeSOAPExtensions._prettyPrint(self.elem) 
     301 
     302 
     303class SOAPFaultException(SOAPFaultExceptionBase): 
     304    """Extend SOAP Fault Exception base class to use ElementTree based  
     305    SOAP Fault implementation for parsing and serialisation""" 
     306    SOAP_FAULT_CLASS = SOAPFault 
     307     
     308     
    140309class SOAPEnvelope(SOAPEnvelopeBase, ETreeSOAPExtensions): 
    141310    """ElementTree based SOAP implementation""" 
     
    195364     
    196365    def parse(self, source): 
    197         self.elem = ETreeSOAPExtensions._parse(self, source)  
     366        """Parse SOAP Envelope""" 
     367        self.elem = self._parse(source)  
    198368         
    199369        for elem in self.elem: 
     
    203373                 
    204374            elif localName == SOAPBody.DEFAULT_ELEMENT_LOCAL_NAME: 
    205                 self.body.elem = elem 
    206                  
     375                self.body.parse(elem) 
    207376            else: 
    208                 raise SOAPFault('Invalid child element in SOAP Envelope "%s" ' 
    209                                 'for stream %r' % (localName, source)) 
     377                faultCode = str(QName(SOAPEnvelopeBase.DEFAULT_ELEMENT_NS,  
     378                                  tag=SOAPFault.CLIENT_FAULT_CODE,  
     379                                  prefix=SOAPFault.DEFAULT_ELEMENT_NS_PREFIX)) 
     380                 
     381                raise SOAPFaultException('Invalid child element in SOAP ' 
     382                                         'Envelope "%s" for stream %r' %  
     383                                         (localName, source), 
     384                                         faultCode) 
  • TI12-security/trunk/ndg_soap/ndg/soap/test/test_soap.py

    r7134 r7663  
    2121from urllib2 import HTTPHandler, URLError 
    2222 
    23 from ndg.soap.etree import SOAPEnvelope 
     23from ndg.soap import SOAPFaultBase 
     24from ndg.soap.etree import SOAPEnvelope, SOAPFault, SOAPFaultException 
    2425from ndg.soap.client import UrlLib2SOAPClient, UrlLib2SOAPRequest 
    2526from ndg.soap.test import PasteDeployAppServer 
     
    4647     
    4748class SOAPTestCase(unittest.TestCase): 
     49    EG_SOAPFAULT_CODE = "%s:%s" % (SOAPFaultBase.DEFAULT_ELEMENT_NS_PREFIX,  
     50                                   "MustUnderstand") 
     51    EG_SOAPFAULT_STRING = "Can't process element X set with mustUnderstand" 
     52         
     53    def test01Envelope(self): 
     54        envelope = SOAPEnvelope() 
     55        envelope.create() 
     56        soap = envelope.serialize() 
     57         
     58        self.assert_(len(soap) > 0) 
     59        self.assert_("Envelope" in soap) 
     60        self.assert_("Body" in soap) 
     61        self.assert_("Header" in soap) 
     62         
     63        print(envelope.prettyPrint()) 
     64        stream = StringIO() 
     65        stream.write(soap) 
     66        stream.seek(0) 
     67         
     68        envelope2 = SOAPEnvelope() 
     69        envelope2.parse(stream) 
     70        soap2 = envelope2.serialize() 
     71        self.assert_(soap2 == soap) 
     72 
     73    def test02CreateSOAPFaultBase(self): 
     74         
     75        fault = SOAPFaultBase(self.__class__.EG_SOAPFAULT_STRING,  
     76                              self.__class__.EG_SOAPFAULT_CODE) 
     77         
     78        self.assert_(fault.faultCode == self.__class__.EG_SOAPFAULT_CODE) 
     79        self.assert_(fault.faultString == self.__class__.EG_SOAPFAULT_STRING) 
     80      
     81    def _createSOAPFault(self): 
     82        fault = SOAPFault(self.__class__.EG_SOAPFAULT_STRING,  
     83                          self.__class__.EG_SOAPFAULT_CODE) 
     84        fault.create() 
     85        return fault    
     86     
     87    def test03SerialiseSOAPFault(self): 
     88        # Use ElementTree implementation 
     89        fault = self._createSOAPFault() 
     90        faultStr = fault.serialize() 
     91        print(faultStr) 
     92        self.assert_(self.__class__.EG_SOAPFAULT_STRING in faultStr) 
     93 
     94    def test04ParseSOAPFault(self): 
     95        fault = self._createSOAPFault() 
     96        faultStr = fault.serialize() 
     97        stream = StringIO() 
     98        stream.write(faultStr) 
     99        stream.seek(0) 
     100         
     101        fault2 = SOAPFault() 
     102        fault2.parse(stream) 
     103        self.assert_(fault2.faultCode == fault.faultCode) 
     104        self.assert_(fault2.faultString == fault.faultString) 
     105     
     106    def test05CreateSOAPFaultException(self): 
     107        try: 
     108            raise SOAPFaultException("bad request", SOAPFault.CLIENT_FAULT_CODE) 
     109         
     110        except SOAPFaultException, e: 
     111            self.assert_(e.fault.faultString == "bad request") 
     112            self.assert_(SOAPFault.CLIENT_FAULT_CODE in e.fault.faultCode) 
     113            e.fault.create() 
     114            self.assert_("bad request" in e.fault.serialize()) 
     115            self.assert_(SOAPFault.CLIENT_FAULT_CODE in e.fault.serialize()) 
     116            return 
     117         
     118        self.fail("Expecting SOAPFaultException raised") 
     119         
     120    def test06CreateSOAPFaultResponse(self): 
     121        # Create full SOAP Response containing a SOAP Fault 
     122        envelope = SOAPEnvelope() 
     123        envelope.body.fault = self._createSOAPFault() 
     124        envelope.create() 
     125        soap = envelope.serialize() 
     126         
     127        self.assert_(len(soap) > 0) 
     128        self.assert_("Envelope" in soap) 
     129        self.assert_("Body" in soap) 
     130        self.assert_("Header" in soap) 
     131        self.assert_("Fault" in soap) 
     132         
     133        print(envelope.prettyPrint()) 
     134        stream = StringIO() 
     135        stream.write(soap) 
     136        stream.seek(0) 
     137         
     138        envelope2 = SOAPEnvelope() 
     139        envelope2.parse(stream) 
     140        soap2 = envelope2.serialize() 
     141        self.assert_(soap2 == soap) 
     142             
     143     
     144class SOAPServiceTestCase(unittest.TestCase): 
    48145    SOAP_SERVICE_PORTNUM = 10080 
    49146    ENDPOINT = 'http://localhost:%d/soap' % SOAP_SERVICE_PORTNUM 
    50     THIS_DIR = path.abspath(path.dirname(__file__)) 
     147    THIS_DIR = path.abspath(path.dirname(__file__))    
    51148     
    52149    def __init__(self, *args, **kwargs): 
     
    58155        self.app = paste.fixture.TestApp(wsgiApp) 
    59156          
    60         super(SOAPTestCase, self).__init__(*args, **kwargs) 
    61          
    62     def test01Envelope(self): 
    63         envelope = SOAPEnvelope() 
    64         envelope.create() 
    65         soap = envelope.serialize() 
    66          
    67         self.assert_(len(soap) > 0) 
    68         self.assert_("Envelope" in soap) 
    69         self.assert_("Body" in soap) 
    70         self.assert_("Header" in soap) 
    71          
    72         print(envelope.prettyPrint()) 
    73         stream = StringIO() 
    74         stream.write(soap) 
    75         stream.seek(0) 
    76          
    77         envelope2 = SOAPEnvelope() 
    78         envelope2.parse(stream) 
    79         soap2 = envelope2.serialize() 
    80         self.assert_(soap2 == soap) 
    81  
    82     def test02SendRequest(self): 
     157        super(SOAPServiceTestCase, self).__init__(*args, **kwargs) 
     158              
     159    def test01SendRequest(self): 
    83160        requestEnvelope = SOAPEnvelope() 
    84161        requestEnvelope.create() 
     
    92169        print(response.body) 
    93170 
    94     def test03Urllib2Client(self): 
     171    def test02Urllib2Client(self): 
    95172         
    96173        # Paster based service is threaded from this call 
    97174        self.addService(app=SOAPBindingMiddleware(),  
    98                         port=SOAPTestCase.SOAP_SERVICE_PORTNUM) 
     175                        port=self.__class__.SOAP_SERVICE_PORTNUM) 
    99176         
    100177        client = UrlLib2SOAPClient() 
     
    104181         
    105182        request = UrlLib2SOAPRequest() 
    106         request.url = SOAPTestCase.ENDPOINT 
     183        request.url = self.__class__.ENDPOINT 
    107184        request.envelope = SOAPEnvelope() 
    108185        request.envelope.create() 
  • TI12-security/trunk/ndg_soap/setup.py

    r7151 r7663  
    4545    license =               'BSD - See LICENCE file for details', 
    4646    install_requires =          _pkgDependencies, 
     47    extras_require = { 
     48        'zsi_middleware':  ["ZSI"], 
     49    }, 
    4750    packages =                  find_packages(), 
    4851    namespace_packages =        ['ndg'], 
Note: See TracChangeset for help on using the changeset viewer.