Changeset 6063


Ignore:
Timestamp:
27/11/09 15:52:29 (10 years ago)
Author:
pjkersha
Message:

Working authz lite integration tests with integrated SAML Attribute Authority interface to authz middleware: the old NDG Attribute Authority SOAP/WSDL interface is completely removed as a dependency.

  • major fixes to ndg.security.common.credentialwallet NDGCredentialWallet and SAMLCredentialWallet for slots and pickling capability needed for beaker.session. NDGCredentialWallet is kept for the moment for backwards compatibility.
Location:
TI12-security/trunk/python
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg_security_common/ndg/security/common/authz/msi.py

    r6062 r6063  
    926926                                 
    927927                except Exception, e: 
    928                     log.error("SAML Attribute Query: %s",  
     928                    log.error("SAML Attribute Query %s: %s",  
    929929                              type(e), traceback.format_exc()) 
    930930                    return Response(Response.DECISION_INDETERMINATE, 
  • TI12-security/trunk/python/ndg_security_common/ndg/security/common/credentialwallet.py

    r6062 r6063  
    169169    CREDENTIAL_TYPE_ATTRNAME = 'type' 
    170170     
    171     __slots__ = ( 
     171    __ATTRIBUTE_NAMES = ( 
    172172        ID_ATTRNAME, 
    173173        ITEM_ATTRNAME, 
     
    176176        CREDENTIAL_TYPE_ATTRNAME 
    177177    ) 
     178    __slots__ = __ATTRIBUTE_NAMES 
    178179    __slots__ += tuple(["_CredentialContainer__%s" % n for n in __slots__]) 
    179180     
     
    217218 
    218219    def _setCredential(self, value): 
    219         if self.type is not None and not isinstance(value, self.type): 
     220        # Safeguard type attribute referencing for unpickling process - this 
     221        # method may be called before type attribute has been set 
     222        _type = getattr(self,  
     223                        CredentialContainer.CREDENTIAL_TYPE_ATTRNAME,  
     224                        None) 
     225         
     226        if _type is not None and not isinstance(value, _type): 
    220227            raise TypeError('Expecting %r type for "credential" attribute; ' 
    221228                            'got %r' % type(value)) 
     
    256263    def __getstate__(self): 
    257264        '''Enable pickling''' 
    258         return dict([(attrName, getattr(self, attrName)) 
    259                      for attrName in self.__class__.__slots__]) 
     265        thisDict = dict([(attrName, getattr(self, attrName)) 
     266                         for attrName in CredentialContainer.__ATTRIBUTE_NAMES]) 
     267         
     268        return thisDict 
    260269         
    261270    def __setstate__(self, attrDict): 
    262271        '''Enable pickling for use with beaker.session''' 
    263         for attr, val in attrDict.items(): 
    264             setattr(self, attr, val) 
     272        try: 
     273            for attr, val in attrDict.items(): 
     274                setattr(self, attr, val) 
     275        except Exception, e: 
     276            pass 
    265277        
    266278 
     
    269281    """  
    270282    CONFIG_FILE_OPTNAMES = ("userId", ) 
    271          
    272     __slots__ = ( 
    273         "credentials", 
    274         "credentialsKeyedByURI", 
    275     ) 
    276     __slots__ += CONFIG_FILE_OPTNAMES 
    277     __slots__ += tuple(["_CredentialWalletBase__%s" % name  
    278                         for name in __slots__]) 
    279     del name 
     283    __slots__ = ("__credentials", "__credentialsKeyedByURI", "__userId") 
     284     
     285    # Helper for __getstate__ 
     286    __PROPERTIES = ("credentials", "credentialsKeyedByURI", "userId") 
     287 
     288#    __slots__ = READ_ONLY_ATTRIBUTES 
     289#    __slots__ += CONFIG_FILE_OPTNAMES 
     290#    __PRIVATE_ATTR_PREFIX = "_CredentialWalletBase__" 
     291#    __slots__ += tuple([__PRIVATE_ATTR_PREFIX + name for name in __slots__]) 
     292#    del name 
    280293     
    281294    def __init__(self): 
     
    386399    def __getstate__(self): 
    387400        '''Enable pickling for use with beaker.session''' 
    388         return dict([(attrName, getattr(self, attrName)) 
    389                      for attrName in self.__class__.__slots__]) 
    390          
     401        _dict = {} 
     402        for attrName in CredentialWalletBase.__slots__: 
     403            # Ugly hack to allow for derived classes setting private member 
     404            # variables 
     405            if attrName.startswith('__'): 
     406                attrName = "_CredentialWalletBase" + attrName 
     407                 
     408            _dict[attrName] = getattr(self, attrName) 
     409             
     410        return _dict 
     411   
    391412    def __setstate__(self, attrDict): 
    392413        '''Enable pickling for use with beaker.session''' 
    393         for attr, val in attrDict.items(): 
    394             setattr(self, attr, val) 
     414        for attrName, val in attrDict.items(): 
     415            setattr(self, attrName, val) 
    395416 
    396417 
     
    558579             
    559580        return True 
    560              
     581     
     582    def __getstate__(self): 
     583        '''Enable pickling for use with beaker.session''' 
     584        _dict = super(SAMLCredentialWallet, self).__getstate__() 
     585         
     586        for attrName in SAMLCredentialWallet.__slots__: 
     587            # Ugly hack to allow for derived classes setting private member 
     588            # variables 
     589            if attrName.startswith('__'): 
     590                attrName = "_SAMLCredentialWallet" + attrName 
     591                 
     592            _dict[attrName] = getattr(self, attrName) 
     593             
     594        return _dict 
     595 
    561596    
    562597class NDGCredentialWallet(CredentialWalletBase): 
     
    670705    __metaclass__ = _MetaCredentialWallet 
    671706 
     707    # Names that may be set in a properties file 
    672708    propertyDefaults = dict( 
    673709        userX509Cert=None, 
     
    683719        mapFromTrustedHosts=False, 
    684720        rtnExtAttCertList=True, 
    685         attCertRefreshElapse=7200, 
     721        attCertRefreshElapse=7200 
     722    ) 
     723     
     724    __slots__ = dict( 
     725        _cfg=None, 
     726        _dn=None, 
     727        _userPriKeyPwd=None, 
     728        _attributeAuthorityClnt=None, 
     729        _attributeAuthorityURI=None, 
     730        sslCACertFilePathList=[], 
    686731        wssCfgFilePath=None, 
    687732        wssCfgSection='DEFAULT', 
    688733        wssCfgPrefix='', 
    689         wssCfgKw={}) 
    690      
    691     _protectedAttrs = [ 
    692         '_userX509Cert', 
    693         '_userX509CertFilePath', 
    694         '_userPriKey', 
    695         '_userPriKeyFilePath', 
    696         '_userPriKeyPwd', 
    697         '_issuingX509Cert', 
    698         '_issuingX509CertFilePath', 
    699         '_attributeAuthorityClnt', 
    700         '_attributeAuthority', 
    701         '_attributeAuthorityURI', 
    702         '_caCertFilePathList', 
    703         '_mapFromTrustedHosts', 
    704         '_rtnExtAttCertList', 
    705         '_attCertRefreshElapse', 
    706         '_cfg', 
    707         '_dn' 
    708     ] 
    709      
    710     __slots__ = propertyDefaults.keys() + _protectedAttrs 
    711      
     734        wssCfgKw={} 
     735    ) 
     736    __slots__.update(dict([("_" + n, v) for n, v in propertyDefaults.items()])) 
     737    del n 
    712738    def __init__(self,  
    713739                 cfg=None,  
     
    726752        from 
    727753        @type cfgPrefix: basestring 
    728         @param cfgPrefix: apply a prefix to all NDGCredentialWallet config params  
    729         so that if placed in a file with other parameters they can be  
     754        @param cfgPrefix: apply a prefix to all NDGCredentialWallet config  
     755        params so that if placed in a file with other parameters they can be  
    730756        distinguished 
    731757        @type cfgKw: dict 
     
    736762        super(NDGCredentialWallet, self).__init__() 
    737763         
    738         # Initialise attributes - 1st protected ones 
    739         attr = {}.fromkeys(NDGCredentialWallet._protectedAttrs) 
    740          
    741         # ... then properties 
    742         attr.update(NDGCredentialWallet.propertyDefaults) 
    743         for k, v in attr.items(): 
     764        # Initialise attributes 
     765        for k, v in NDGCredentialWallet.__slots__.items(): 
    744766            setattr(self, k, v) 
    745767             
     
    760782 
    761783        # Make a connection to the Credentials Repository 
    762         if self.credentialRepository is None: 
     784        if self._credentialRepository is None: 
    763785            log.info('Applying default CredentialRepository %r for user ' 
    764786                     '"%s"' % (NullCredentialRepository, self.userId)) 
    765             self.credentialRepository = NullCredentialRepository() 
     787            self._credentialRepository = NullCredentialRepository() 
    766788        else: 
    767789            log.info('Checking CredentialRepository for credentials for user ' 
    768790                     '"%s"' % self.userId) 
    769791             
    770             if not issubclass(self.credentialRepository, CredentialRepository): 
     792            if not issubclass(self._credentialRepository, CredentialRepository): 
    771793                raise CredentialWalletError("Input Credential Repository " 
    772794                                            "instance must be of a class " 
     
    777799            # Check for valid attribute certificates for the user 
    778800            try: 
    779                 self.credentialRepository.auditCredentials(self.userId) 
    780                 userCred=self.credentialRepository.getCredentials(self.userId) 
     801                self._credentialRepository.auditCredentials(self.userId) 
     802                userCred=self._credentialRepository.getCredentials(self.userId) 
    781803     
    782804            except Exception, e: 
     
    809831            self.audit() 
    810832     
     833    def __getstate__(self): 
     834        '''Enable pickling for use with beaker.session''' 
     835        _dict = super(NDGCredentialWallet, self).__getstate__() 
     836         
     837        for attrName in SAMLCredentialWallet.__slots__: 
     838            # Ugly hack to allow for derived classes setting private member 
     839            # variables 
     840            if attrName.startswith('__'): 
     841                attrName = "_NDGCredentialWallet" + attrName 
     842                 
     843            _dict[attrName] = getattr(self, attrName) 
     844             
     845        return _dict 
     846         
    811847    def parseConfig(self, cfg, prefix='', section='DEFAULT'): 
    812848        '''Extract parameters from cfg config object''' 
     
    12581294            # authorisation credentials.  This allows credentials for previous 
    12591295            # sessions to be re-instated 
    1260             if self.credentialRepository and bUpdateCredentialRepository: 
     1296            if self._credentialRepository and bUpdateCredentialRepository: 
    12611297                self.updateCredentialRepository() 
    12621298 
     
    12921328        log.debug("NDGCredentialWallet.updateCredentialRepository ...") 
    12931329         
    1294         if not self.credentialRepository: 
     1330        if not self._credentialRepository: 
    12951331            raise CredentialWalletError("No Credential Repository has been " 
    12961332                                        "created for this wallet") 
     
    13041340                       if i.id == -1] 
    13051341 
    1306         self.credentialRepository.addCredentials(self.userId, attCertList) 
     1342        self._credentialRepository.addCredentials(self.userId, attCertList) 
    13071343 
    13081344    def _createAttributeAuthorityClnt(self, attributeAuthorityURI): 
  • TI12-security/trunk/python/ndg_security_common/ndg/security/common/soap/client.py

    r5741 r6063  
    216216        if response.code != httplib.OK: 
    217217            output = response.read() 
    218             excep = HTTPException("Response is: %d %s" % (response.code,  
    219                                                           response.msg)) 
     218            excep = HTTPException("Response for request to [%s] is: %d %s" %  
     219                                  (soapRequest.url,  
     220                                   response.code,  
     221                                   response.msg)) 
    220222            excep.urllib2Response = response 
    221223            raise excep 
    222224         
    223         if response.headers.typeheader not in \ 
    224            UrlLib2SOAPClient.RESPONSE_CONTENT_TYPES: 
     225        if (response.headers.typeheader not in  
     226            UrlLib2SOAPClient.RESPONSE_CONTENT_TYPES): 
    225227            responseType = ', '.join(UrlLib2SOAPClient.RESPONSE_CONTENT_TYPES) 
    226228            excep = SOAPResponseError("Expecting %r response type; got %r for " 
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/config/attributeauthority/sitea/siteAUserRoles.py

    r6062 r6063  
    8181    SAML_ASSERTION_LIFETIME = 8*60*60 
    8282     
    83     VALID_USER_IDS = ("https://openid.localhost/philip.kershaw",) 
     83    VALID_USER_IDS = ("https://openid.localhost/philip.kershaw", 
     84                      "https://localhost:7443/openid/PhilipKershaw") 
    8485    VALID_REQUESTOR_IDS = ( 
    8586        str(X500DN.fromString("/O=Site A/CN=Authorisation Service")),  
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/integration/authz_lite/policy.xml

    r5447 r6063  
    11<?xml version="1.0" encoding="UTF-8"?> 
    2 <Policy PolicyId="pyDAP" xmlns="urn:ndg:security:authz:1.0:policy"> 
     2<Policy PolicyId="AuthZ Lite - Authorisation Integration Tests" xmlns="urn:ndg:security:authz:1.1:policy"> 
    33    <Description>Restrict access for Authorization integration tests</Description> 
    44     
     
    66        <URIPattern>^/test_securedURI*$</URIPattern> 
    77        <Attributes> 
    8             <Attribute>urn:siteA:security:authz:1.0:attr:staff</Attribute> 
     8            <Attribute> 
     9                <Name>urn:siteA:security:authz:1.0:attr:staff</Name> 
     10                <AttributeAuthorityURI>https://localhost:7443/AttributeAuthority/saml</AttributeAuthorityURI> 
     11            </Attribute> 
    912        </Attributes> 
    10         <AttributeAuthority> 
    11             <uri>http://localhost:7443/AttributeAuthority</uri> 
    12         </AttributeAuthority> 
    1313    </Target> 
    1414    <Target> 
    1515        <URIPattern>^/test_accessDeniedToSecuredURI$</URIPattern> 
    1616        <Attributes> 
    17             <Attribute>urn:siteA:security:authz:1.0:attr:forbidden</Attribute> 
    18             <Attribute>urn:siteA:security:authz:1.0:attr:keepout</Attribute> 
     17            <Attribute> 
     18                <Name>urn:siteA:security:authz:1.0:attr:forbidden</Name> 
     19                <AttributeAuthorityURI>https://localhost:7443/AttributeAuthority/saml</AttributeAuthorityURI> 
     20            </Attribute> 
     21            <Attribute> 
     22                <Name>urn:siteA:security:authz:1.0:attr:keepout</Name> 
     23                <AttributeAuthorityURI>https://localhost:7443/AttributeAuthority/saml</AttributeAuthorityURI> 
     24            </Attribute> 
    1925        </Attributes> 
    20         <AttributeAuthority> 
    21             <uri>http://localhost:7443/AttributeAuthority</uri> 
    22         </AttributeAuthority> 
    2326    </Target> 
    2427</Policy> 
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/integration/authz_lite/securedapp.ini

    r6059 r6063  
    5555 
    5656# Set redirect for OpenID Relying Party in the Security Services app instance 
    57 authN.redirectURI = http://localhost:7443/verify 
     57authN.redirectURI = https://localhost:7443/verify 
    5858# Test with an SSL endpoint 
    5959#authN.redirectURI = https://localhost/verify 
     
    7676 
    7777[filter:AuthorizationFilter] 
    78 paste.filter_app_factory=ndg.security.server.wsgi.authz:AuthorizationMiddleware.filter_app_factory 
     78paste.filter_app_factory=ndg.security.server.wsgi.authz:SAMLAuthorizationMiddleware.filter_app_factory 
    7979prefix = authz. 
    8080policy.filePath = %(here)s/policy.xml 
     
    8383# retrieve subject attributes from the Attribute Authority associated with the 
    8484# resource to be accessed 
    85 pip.sslCACertFilePathList= 
    8685 
    87 # List of CA certificates used to verify the signatures of  
    88 # Attribute Certificates retrieved 
    89 pip.caCertFilePathList=%(testConfigDir)s/ca/ndg-test-ca.crt 
    90  
    91 # 
    92 # WS-Security Settings for call to Attribute Authority to retrieve user  
    93 # attributes 
    94  
    95 # Signature of an outbound message 
    96  
    97 # Certificate associated with private key used to sign a message.  The sign  
    98 # method will add this to the BinarySecurityToken element of the WSSE header.   
    99 # binSecTokValType attribute must be set to 'X509' or 'X509v3' ValueType.   
    100 # As an alternative, use signingCertChain - see below... 
    101  
    102 # PEM encode cert 
    103 pip.wssecurity.signingCertFilePath=%(testConfigDir)s/pki/wsse-server.crt 
    104  
    105 # PEM encoded private key file 
    106 pip.wssecurity.signingPriKeyFilePath=%(testConfigDir)s/pki/wsse-server.key 
    107  
    108 # Password protecting private key.  Leave blank if there is no password. 
    109 pip.wssecurity.signingPriKeyPwd= 
    110  
    111 # For signature verification.  Provide a space separated list of file paths 
    112 pip.wssecurity.caCertFilePathList=%(testConfigDir)s/ca/ndg-test-ca.crt 
    113  
    114 # ValueType for the BinarySecurityToken added to the WSSE header 
    115 pip.wssecurity.reqBinSecTokValType=X509v3 
    116  
    117 # Add a timestamp element to an outbound message 
    118 pip.wssecurity.addTimestamp=True 
     86# If omitted, DN of SSL Cert is used 
     87pip.attributeQuery.issuerName =  
     88pip.attributeQuery.clockSkew = 0. 
     89pip.attributeQuery.queryAttributes.0 = urn:siteA:security:authz:1.0:attr, , http://www.w3.org/2001/XMLSchema#string 
     90pip.attributeQuery.sslCACertDir=%(testConfigDir)s/ca 
     91pip.attributeQuery.sslCertFilePath=%(testConfigDir)s/pki/test.crt 
     92pip.attributeQuery.sslPriKeyFilePath=%(testConfigDir)s/pki/test.key 
    11993 
    12094# Logging configuration 
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/integration/authz_lite/securityservices.ini

    r5984 r6063  
    1717portNum = 7443 
    1818hostname = localhost 
    19 scheme = http 
     19scheme = https 
    2020baseURI = %(scheme)s://%(hostname)s:%(portNum)s 
    2121openIDProviderIDBase = /openid 
     
    311311# Settings for custom AttributeInterface derived class to get user roles for given  
    312312# user ID 
    313 #attributeAuthority.attributeInterface.modFilePath: %(testConfigDir)s/attributeauthority/sitea 
    314 attributeAuthority.attributeInterface.modName: ndg.security.test.integration.authz_lite.attributeinterface 
     313attributeAuthority.attributeInterface.modFilePath: %(testConfigDir)s/attributeauthority/sitea 
     314attributeAuthority.attributeInterface.modName: siteAUserRoles 
    315315attributeAuthority.attributeInterface.className: TestUserRoles 
    316316 
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/integration/authz_lite/securityservicesapp.py

    r5779 r6063  
    1212import os 
    1313from os.path import dirname, abspath, join 
     14       
     15from OpenSSL import SSL 
    1416 
     17from ndg.security.test.unit import BaseTestCase 
    1518from ndg.security.test.unit.wsgi import PasteDeployAppServer 
     19 
     20INI_FILEPATH = 'securityservices.ini' 
    1621 
    1722# To start run  
     
    2530        port = 7443 
    2631         
    27     cfgFileName='securityservices.ini' 
    28     cfgFilePath = os.path.join(dirname(abspath(__file__)), cfgFileName)     
    29     server = PasteDeployAppServer(cfgFilePath=cfgFilePath, port=port)  
     32    cfgFileName = INI_FILEPATH 
     33    cfgFilePath = os.path.join(dirname(abspath(__file__)), cfgFileName)   
     34     
     35    certFilePath = os.path.join(BaseTestCase.NDGSEC_TEST_CONFIG_DIR,  
     36                                'pki',  
     37                                'localhost.crt') 
     38    priKeyFilePath = os.path.join(BaseTestCase.NDGSEC_TEST_CONFIG_DIR,  
     39                                  'pki',  
     40                                  'localhost.key') 
     41     
     42    ssl_context = SSL.Context(SSL.SSLv23_METHOD) 
     43    ssl_context.set_options(SSL.OP_NO_SSLv2) 
     44 
     45    ssl_context.use_privatekey_file(priKeyFilePath) 
     46    ssl_context.use_certificate_file(certFilePath) 
     47 
     48    server = PasteDeployAppServer(cfgFilePath=cfgFilePath,  
     49                                  port=port, 
     50                                  ssl_context=ssl_context)  
    3051    server.start() 
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/unit/credentialwallet/test_credentialwallet.py

    r6062 r6063  
    1717from string import Template 
    1818from cStringIO import StringIO 
     19import cPickle as pickle 
     20 
    1921from elementtree import ElementTree 
    2022 
     
    4648    ndg.security.common.credentialwallet.NDGCredentialWallet class. 
    4749    """ 
     50    THIS_DIR = os.path.dirname(__file__) 
     51    PICKLE_FILENAME = 'NDGCredentialWalletPickle.dat' 
     52    PICKLE_FILEPATH = os.path.join(THIS_DIR, PICKLE_FILENAME) 
     53 
    4854    def __init__(self, *arg, **kw): 
    4955        super(NDGCredentialWalletTestCase, self).__init__(*arg, **kw) 
     
    108114             
    109115        credWallet.attributeAuthority = None 
    110         credWallet.credentialRepository = None 
     116        credWallet._credentialRepository = None 
    111117        credWallet.mapFromTrustedHosts = False 
    112118        credWallet.rtnExtAttCertList = True 
     
    159165            attCert = credWallet.getAttCert() 
    160166        except CredentialWalletAttributeRequestDenied, e: 
    161             print("SUCCESS - obtained expected result: %s" % e) 
     167            print("ok - obtained expected result: %s" % e) 
    162168            return 
    163169         
     
    196202        print("Attribute Certificate:\n%s" % attCert)   
    197203 
     204    def test08Pickle(self): 
     205        credWallet = NDGCredentialWallet(cfg=self.cfg.get('setUp',  
     206                                                          'cfgFilePath')) 
     207 
     208        outFile = open(NDGCredentialWalletTestCase.PICKLE_FILEPATH, 'w') 
     209        pickle.dump(credWallet, outFile) 
     210        outFile.close() 
     211         
     212        inFile = open(NDGCredentialWalletTestCase.PICKLE_FILEPATH) 
     213        unpickledCredWallet = pickle.load(inFile) 
     214        self.assert_(unpickledCredWallet.userId == credWallet.userId) 
     215         
    198216 
    199217class SAMLCredentialWalletTestCase(BaseTestCase): 
     
    201219    CONFIG_FILENAME = 'test_samlcredentialwallet.cfg' 
    202220    CONFIG_FILEPATH = os.path.join(THIS_DIR, CONFIG_FILENAME) 
     221    PICKLE_FILENAME = 'SAMLCredentialWalletPickle.dat' 
     222    PICKLE_FILEPATH = os.path.join(THIS_DIR, PICKLE_FILENAME) 
    203223     
    204224    ASSERTION_STR = ( 
     
    321341        wallet.addCredential(self._createAssertion(issuerName="MySite")) 
    322342        self.assert_(len(wallet.credentials) == 2) 
     343 
     344    def test06Pickle(self): 
     345        wallet = self._addCredential() 
     346        outFile = open(SAMLCredentialWalletTestCase.PICKLE_FILEPATH, 'w') 
     347        pickle.dump(wallet, outFile) 
     348        outFile.close() 
     349         
     350        inFile = open(SAMLCredentialWalletTestCase.PICKLE_FILEPATH) 
     351        unpickledWallet = pickle.load(inFile) 
     352        self.assert_(unpickledWallet.credentialsKeyedByURI.get( 
     353            SAMLCredentialWalletTestCase.SITEA_ATTRIBUTEAUTHORITY_SAML_URI)) 
    323354         
    324355         
  • TI12-security/trunk/python/ndg_security_test/setup.py

    r6009 r6063  
    2929    url =                       'http://proj.badc.rl.ac.uk/ndg/wiki/Security', 
    3030    license =               'BSD - See LICENCE file for details', 
     31    install_requires =      'pyOpenSSL', # Required for paster to run under SSL 
    3132    packages =                      find_packages(), 
    3233    namespace_packages =        ['ndg', 'ndg.security'], 
Note: See TracChangeset for help on using the changeset viewer.