Changeset 6202 for TI12-security


Ignore:
Timestamp:
21/12/09 16:40:20 (10 years ago)
Author:
pjkersha
Message:

Adding information about requested attributes for the decide page interface of the OpenID Provider.

Location:
TI12-security/trunk/python
Files:
2 added
3 deleted
14 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/MyProxyClient/myproxy/client.py

    r6107 r6202  
    10651065                                                    (nCerts, len(pemCerts))) 
    10661066     
    1067         # Return certs and private key 
    1068         # - proxy cert 
    1069         # - private key 
    1070         # - rest of cert chain 
    1071         if keys is not None: 
    1072             pemKey = keys.as_pem(cipher=None) 
     1067        if key is not None: 
     1068            # Return certs and private key 
     1069            # - proxy or dynamically issued certificate (MyProxy CA mode) 
     1070            # - private key 
     1071            # - rest of cert chain if proxy cert issued 
     1072            pemKey = key.as_pem(cipher=None) 
    10731073            creds = [pemCerts[0], pemKey] 
    10741074            creds.extend(pemCerts[1:]) 
    10751075        else: 
     1076            # Key generated externally - return certificate chain only 
    10761077            creds = pemCerts 
     1078 
    10771079         
    10781080        return tuple(creds) 
  • TI12-security/trunk/python/ndg_security_common/ndg/security/common/credentialwallet.py

    r6113 r6202  
    3838    aaImportError = False 
    3939except ImportError: 
    40     log.warning('Loading CredentialWallet without SOAP interface imports: %s',  
    41                 traceback.format_exc()) 
     40    pass 
    4241 
    4342# Likewise - may not want to use WS and use AttributeAuthority locally in which 
     
    4847    aaImportError = False 
    4948except ImportError: 
    50     log.warning('Loading CredentialWallet without Attribute Authority ' 
    51                 'interface imports: %s', traceback.format_exc()) 
     49    pass 
    5250 
    5351if aaImportError: 
    5452    raise ImportError("Either AttributeAuthority or AttributeAuthorityClient " 
    5553                      "classes must be present to allow interoperation with " 
    56                       "Attribute Authorities") 
     54                      "Attribute Authorities: %s" % traceback.format_exc()) 
    5755 
    5856# Authentication X.509 Certificate 
  • TI12-security/trunk/python/ndg_security_saml/ndg_security_saml.egg-info/PKG-INFO

    r6107 r6202  
    11Metadata-Version: 1.0 
    22Name: ndg-security-saml 
    3 Version: 0.1.3rc1-r6069 
     3Version: 0.1.3rc1-r6107 
    44Summary: NERC DataGrid SAML Implementation 
    55Home-page: http://proj.badc.rl.ac.uk/ndg/wiki/Security 
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/myproxy/__init__.py

    r6107 r6202  
    1616import socket 
    1717 
     18from M2Crypto import X509 
     19 
    1820from myproxy.client import MyProxyClient, MyProxyClientError 
    19 from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \ 
    20     NDGSecurityMiddlewareConfigError 
     21from ndg.security.server.wsgi import (NDGSecurityMiddlewareBase,  
     22                                      NDGSecurityMiddlewareConfigError) 
    2123     
    2224from ndg.security.server.wsgi.authn import HTTPBasicAuthMiddleware 
     
    4143    LOGON_FUNC_ENV_KEYNAME = ('ndg.security.server.wsgi.authn.' 
    4244                              'MyProxyClientMiddleware.logon') 
     45     
     46    WSGI_INPUT_ENV_KEYNAME = 'wsgi.input' 
    4347     
    4448    # Option prefixes 
     
    138142            """Wrap MyProxy logon method as a WSGI app 
    139143            """ 
     144            if environ['HTTP_METHOD'] == 'GET': 
     145                # No certificate request passed with GET call 
     146                # TODO: retire this method? - keys are generated here instead of 
     147                # the client 
     148                certReq = None 
     149                     
     150            elif environ['HTTP_METHOD'] == 'POST': 
     151                 
     152                pemCertReq = environ[ 
     153                        MyProxyClientMiddleware.WSGI_INPUT_ENV_KEYNAME].read() 
     154                 
     155                # TODO: restore WSGI file object 
     156                 
     157                # Expecting PEM encoded request 
     158                certReq = X509.load_request_string(pemCertReq)   
     159            else: 
     160                status = self.getStatusMessage(httplib.UNAUTHORIZED) 
     161                response = ("HTTP request method %r not recognised for this " 
     162                            "request " % environ['HTTP_METHOD']) 
     163                 
    140164            try: 
    141                 credentials = self.myProxyClient.logon(username, password) 
     165                credentials = self.myProxyClient.logon(username,  
     166                                                       password, 
     167                                                       certReq=certReq) 
    142168                status = self.getStatusMessage(httplib.OK) 
    143169                response = '\n'.join(credentials) 
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/__init__.py

    r6069 r6202  
    177177     
    178178    IDENTITY_URI_TMPL_PARAMNAME = 'identityUriTemplate' 
     179     
     180    # Approve and reject submit HTML input types for the Relying Party Approval  
     181    # page 
     182    APPROVE_RP_SUBMIT = "ApproveRelyingParty" 
     183    REJECT_RP_SUBMIT = "RejectRelyingParty" 
    179184     
    180185    def __init__(self, app, app_conf=None, prefix=PARAM_PREFIX, **kw): 
     
    640645                                          code=400) 
    641646         
    642         if 'Yes' in self.query: 
     647        if OpenIDProviderMiddleware.APPROVE_RP_SUBMIT in self.query: 
    643648            if oidRequest.idSelect(): 
    644649                identity = self.query.get('identity') 
     
    657662 
    658663            trust_root = oidRequest.trust_root 
    659             if self.query.get('remember', 'No') == 'Yes': 
     664            if self.query.get('remember', 'no').lower() == 'yes': 
    660665                self.session[ 
    661666                    OpenIDProviderMiddleware.APPROVED_FLAG_SESSION_KEYNAME] = { 
     
    670675            except (OpenIDProviderMissingRequiredAXAttrs, 
    671676                    OpenIDProviderMissingAXResponseHandler): 
     677                log.error("%s type exception raised setting response " 
     678                          "following ID Approval: %s",  
     679                          e.__class__.__name__, 
     680                          traceback.format_exc()) 
     681                 
    672682                response = self._render.errorPage(environ, start_response, 
    673                     'The site where you wish to signin requires ' 
    674                     'additional information which this site isn\'t ' 
    675                     'configured to provide.  Please report this fault to ' 
    676                     'your site administrator.') 
     683                        'The site where you wish to signin requires ' 
     684                        'additional information which this site isn\'t ' 
     685                        'configured to provide.  Please report this fault to ' 
     686                        'your site administrator.') 
    677687                return response 
    678688                     
    679689            except Exception, e: 
    680690                log.error("%s type exception raised setting response " 
    681                           "following ID Approval: %s", e.__class__.__name__,e) 
     691                          "following ID Approval: %s",  
     692                          e.__class__.__name__, 
     693                          traceback.format_exc()) 
    682694                return self._render.errorPage(environ, start_response, 
    683695                        'An error occurred setting additional parameters ' 
     
    687699                return self._displayResponse(oidResponse) 
    688700         
    689         elif 'No' in self.query: 
     701        elif OpenIDProviderMiddleware.REJECT_RP_SUBMIT in self.query: 
    690702            # TODO: Check 'No' response is OK - No causes AuthKit's Relying  
    691703            # Party implementation to crash with 'openid.return_to' KeyError 
     
    900912        """ 
    901913        oidRequest = self.session.get( 
    902                     OpenIDProviderMiddleware.LAST_CHECKID_REQUEST_SESSION_KEYNAME) 
     914                OpenIDProviderMiddleware.LAST_CHECKID_REQUEST_SESSION_KEYNAME) 
    903915        if oidRequest is None: 
    904916            log.error("No OpenID request set in session") 
     
    909921                                          code=400) 
    910922         
    911         approvedRoots = self.session.get( 
    912                             OpenIDProviderMiddleware.APPROVED_FLAG_SESSION_KEYNAME,  
    913                             {}) 
    914          
    915         if oidRequest.trust_root in approvedRoots and \ 
    916            not oidRequest.idSelect(): 
    917             try: 
    918                 response = self._identityApprovedPostProcessing(oidRequest, 
    919                                                         oidRequest.identity) 
    920             except (OpenIDProviderMissingRequiredAXAttrs, 
    921                     OpenIDProviderMissingAXResponseHandler): 
    922                 response = self._render.errorPage(environ, start_response, 
    923                     'The site where you wish to signin requires ' 
    924                     'additional information which this site isn\'t ' 
    925                     'configured to provide.  Please report this fault to ' 
     923        try: 
     924            return self._render.decidePage(environ,  
     925                                           start_response,  
     926                                           oidRequest) 
     927        except AuthNInterfaceError, e: 
     928            log.error("%s type exception raised calling decide page " 
     929                      "rendering - an OpenID identifier look-up error? " 
     930                      "message is: %s", e.__class__.__name__, 
     931                      traceback.format_exc()) 
     932            response = self._render.errorPage(environ, start_response, 
     933                    'An error has occurred displaying an options page ' 
     934                    'which checks whether you want to return to the site ' 
     935                    'requesting your ID.  Please report this fault to ' 
    926936                    'your site administrator.') 
    927                 return response 
    928                      
    929             except Exception, e: 
    930                 log.error("%s type exception raised setting response " 
    931                           "following ID Approval: %s",  
    932                           e.__class__.__name__, 
    933                           traceback.format_exc()) 
    934                 response = self._render.errorPage(environ, start_response, 
    935                         'An error occurred setting additional parameters ' 
    936                         'required by the site requesting your ID.  Please ' 
    937                         'report this fault to your site administrator.') 
    938                 return response 
    939  
    940             return self.oidResponse(response) 
    941         else: 
    942             try: 
    943                 return self._render.decidePage(environ,  
    944                                                start_response,  
    945                                                oidRequest) 
    946             except AuthNInterfaceError, e: 
    947                 log.error("%s type exception raised calling decide page " 
    948                           "rendering - an OpenID identifier look-up error? " 
    949                           "message is: %s", e.__class__.__name__, 
    950                           traceback.format_exc()) 
    951                 response = self._render.errorPage(environ, start_response, 
    952                         'An error has occurred displaying an options page ' 
    953                         'which checks whether you want to return to the site ' 
    954                         'requesting your ID.  Please report this fault to ' 
    955                         'your site administrator.') 
    956                 return response 
     937            return response 
    957938 
    958939    def _identityIsAuthorized(self, oidRequest): 
     
    11271108            # User is logged in - check for ID Select type request i.e. the 
    11281109            # user entered their IdP address at the Relying Party and not their 
    1129             # OpenID Identifier.  In this case, the identity they wish to use 
    1130             # must be confirmed. 
     1110            # full OpenID URL.  In this case, the identity they wish to use must 
     1111            # be confirmed. 
    11311112            if oidRequest.idSelect(): 
    11321113                # OpenID identifier must be confirmed 
     
    11341115             
    11351116            elif self._trustRootIsAuthorized(oidRequest.trust_root): 
    1136                 # User has approved this Relying Party 
     1117                # User entered their full OpenID URL and they have previously  
     1118                # approved this Relying Party 
    11371119                try: 
    11381120                    oidResponse = self._identityApprovedPostProcessing( 
     
    11701152                return self._displayResponse(oidResponse) 
    11711153            else: 
     1154                # This OpenID is being used for a login for the first time.   
     1155                # Check with the user whether they want to approval the Relying 
     1156                # Party's request 
    11721157                return self.do_decide(self.environ, self.start_response) 
    11731158                 
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/axinterface/sqlalchemy_ax.py

    r6069 r6202  
    163163        userAttributeMap = self._attributeQuery(username) 
    164164         
    165         # Add the required attributes 
    166         for requiredAttributeURI in requiredAttributeURIs: 
    167             log.info("Adding required AX parameter %s=%s ...",  
    168                      requiredAttributeURI, 
    169                      userAttributeMap[requiredAttributeURI]) 
    170              
    171             ax_resp.addValue(requiredAttributeURI, 
    172                              userAttributeMap[requiredAttributeURI]) 
    173              
    174         # Append requested attribute if available 
     165        # Add the requested attribute if available 
    175166        for requestedAttributeURI in ax_req.requested_attributes.keys(): 
    176167            if requestedAttributeURI in self.attributeNames: 
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/renderinginterface/genshi/__init__.py

    r6127 r6202  
    1 """NDG Security Pylons Buffet based Rendering Interface for  
     1"""NDG Security Genshi based Rendering Interface for  
    22OpenIDProviderMiddleware 
    33 
     
    1919from openid.consumer import discover 
    2020from openid.server.server  import CheckIDRequest 
     21from openid.extensions import ax 
    2122 
    2223# Rendering classes for OpenID Provider must derive from generic render  
     
    6869    ERROR_PAGE_TMPL_NAME = 'error.html' 
    6970     
     71    # Approve and reject submit HTML input types for the Relying Party Approval  
     72    # page 
     73    APPROVE_RP_SUBMIT = OpenIDProviderMiddleware.APPROVE_RP_SUBMIT 
     74    REJECT_RP_SUBMIT = OpenIDProviderMiddleware.REJECT_RP_SUBMIT 
     75 
    7076    DEFAULT_TEMPLATES_DIR = path.join(path.dirname(__file__), 'templates') 
    7177     
     
    350356        self.trust_root = oidRequest.trust_root 
    351357        self.oidRequest = oidRequest 
     358        ax_req = ax.FetchRequest.fromOpenIDRequest(oidRequest) 
     359        axRequestedAttr = ax_req.requested_attributes 
    352360        self.environ = environ 
    353361         
     
    373381            self.identityURI = oidRequest.identity 
    374382         
    375         response = self._render(GenshiRendering.DECIDE_PAGE_TMPL_NAME) 
     383        response = self._render(GenshiRendering.DECIDE_PAGE_TMPL_NAME, 
     384                                axRequestedAttr=axRequestedAttr) 
    376385        self.identityURI = '' 
    377386         
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/provider/renderinginterface/genshi/templates/decide.html

    r6127 r6202  
    1616                                        <td> 
    1717                                                The website <b>$c.oidRequest.trust_root</b> has requested  
    18                                                 your OpenID identifier: 
     18                                                your OpenID: 
    1919                                        </td> 
    2020                                </tr> 
     
    2424                                </td> 
    2525                        </tr> 
     26                                <tr py:if="len(axRequestedAttr) > 0"> 
     27                                    <td> 
     28                                                <p>$c.oidRequest.trust_root has requested these additional items of information.  Optional  
     29                                                items may be omitted from the response by unchecking the relevant checkboxes:</p> 
     30                                         <table id="opAXRequestedAttributes"> 
     31                                             <tbody> 
     32                                                <th> 
     33                                                    <td>Item</td> 
     34                                                    <td>Return Item to Requesting Site?</td> 
     35                                                </th> 
     36                                                                                <tr py:for="i in axRequestedAttr.values()"> 
     37                                                                                    <td>${i.alias or i.type_uri}</td> 
     38                                                                                    <td py:if="i.required">&nbsp;</td> 
     39                                                                                    <td py:if="not i.required"><input  type="checkbox" id="${i}.returnToRP" name="${i}.returnToRP" checked="checked" value="yes"/></td> 
     40                                                                                </tr>  
     41                                                                 </tbody> 
     42                                                            </table> 
     43                                    </td> 
     44                                </tr> 
    2645                                <tr> 
    2746                                        <td>                                             
     
    3352                                <tr> 
    3453                                        <td align="right"> 
    35                                                 <input type="submit" name="Yes" value="Yes" /> 
    36                                                 <input type="submit" name="No" value="No" /> 
     54                                                <input type="submit" name="$c.APPROVE_RP_SUBMIT" value="Yes" /> 
     55                                                <input type="submit" name="$c.REJECT_RP_SUBMIT" value="No" /> 
    3756                                        </td> 
    3857                                </tr> 
     
    4059                                        <td align="right"> 
    4160                                                <div py:if="c.oidRequest.trust_root not in c.session.get('approved', {})"> 
    42                                                         <input type="checkbox" id="remember" name="remember" value="yes"/> 
     61                                                        <input type="checkbox" id="remember" name="remember" value="Yes"/> 
    4362                                                        <label for="remember">Remember this decision for session duration</label> 
    4463                                                </div> 
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/relyingparty/signin_interface/genshi/public/layout/default.css

    r6127 r6202  
    1212        line-height:1.4; 
    1313        font-size: small; 
     14} 
     15 
     16/* 
     17* Provider Attribute Exchange Request parameters 
     18*/ 
     19#opAXRequestedAttributes { 
     20        width: 100%; 
     21        border-collapse: collapse; 
     22        background-color: #eef; 
     23        table-layout: auto; 
     24} 
     25 
     26#opAXRequestedAttributes td, #opAXRequestedAttributes th  
     27{ 
     28font-size:1em; 
     29border:1px solid #dde; 
     30padding:3px 7px 2px 7px; 
     31} 
     32 
     33#opAXRequestedAttributes th  
     34{ 
     35font-size:1.1em; 
     36text-align:left; 
     37padding-top:5px; 
     38padding-bottom:4px; 
     39background-color: #ccd; 
     40color:#ffffff; 
    1441} 
    1542 
     
    99126#identityUriBox { 
    100127    color: black; 
    101     background-color: #cccccc; 
     128    background-color: #eef; 
    102129    margin-top:20px;  
    103130    margin-bottom: 20px;  
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/relyingparty/signin_interface/genshi/templates/base.html

    r6127 r6202  
    2525        <table width="100%"><tbody> 
    2626            <tr> 
    27                 <td align="left" width="60%"> 
     27                <td align="left" width="60%" valign="top"> 
    2828                    <table> 
    2929                        <tbody> 
    3030                            <tr> 
    31                                 <td py:if="c.leftImage and c.leftLink"><span py:replace="linkimage(c.leftLink, c.leftImage, c.leftAlt)"/></td> 
    32                                 <td py:if="c.footerText">${HTML(c.footerText)}</td> 
     31                                <td valign="top" py:if="c.leftImage and c.leftLink"><span py:replace="linkimage(c.leftLink, c.leftImage, c.leftAlt)"/></td> 
     32                                <td valign="top" py:if="c.footerText">${HTML(c.footerText)}</td> 
    3333                            </tr> 
    3434                        </tbody> 
    3535                    </table> 
    3636                </td> 
    37                 <td align="right" width="40%"><span py:replace="linkimage(c.rightLink, c.rightImage, c.rightAlt)"/></td> 
     37                <td align="right" valign="top" width="40%"><span py:replace="linkimage(c.rightLink, c.rightImage, c.rightAlt)"/></td> 
    3838            </tr> 
    3939        </tbody></table> 
  • TI12-security/trunk/python/ndg_security_server/setup.py

    r6134 r6202  
    2222_pkgDependencies = [ 
    2323    'ndg_security_common', 
    24     'AuthKit', 
     24    'Paste', 
     25    'AuthKit==0.4.3rc3_ndg_r174', 
    2526    'MyProxyClient' 
    2627] 
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/integration/authz_lite/securityservices.ini

    r6127 r6202  
    181181#authkit.openid.ax.alias.firstName=first_name 
    182182#authkit.openid.ax.count.firstName=1 
     183#authkit.openid.ax.required.firstName=True 
    183184authkit.openid.ax.typeuri.lastName=http://openid.net/schema/namePerson/last 
     185authkit.openid.ax.required.lastName=True 
    184186authkit.openid.ax.typeuri.emailAddress=http://openid.net/schema/contact/internet/email 
    185 #authkit.openid.ax.required.emailAddress=True 
     187authkit.openid.ax.required.emailAddress=True 
    186188 
    187189 
     
    209211# <meta http-equiv="x-xrds-location" content="..."> links if required but in  
    210212# this implementation it set to return 404 not found - see  
    211 # ndg.security.server.wsgi.openid.provider.renderinginterface.buffet.BuffetRendering  
     213# ndg.security.server.wsgi.openid.provider.renderinginterface.genshi.GenshiRendering  
    212214# class 
    213215openid.provider.path.id=/OpenID/Provider/id/${userIdentifier} 
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/unit/wsgi/myproxy/test_myproxy.py

    r6107 r6202  
    1616import urllib2 
    1717import base64 
     18 
    1819import paste.fixture 
    1920from paste.deploy import loadapp 
     21 
     22from M2Crypto import X509 
     23 
    2024from ndg.security.test.unit import BaseTestCase 
    2125from ndg.security.common.utils.configfileparsers import \ 
     
    129133        BaseTestCase.__init__(self, *args, **kwargs) 
    130134 
     135        # Thread separate Paster based service  
     136        self.addService(app=self.wsgiapp,  
     137                        port=MyProxyLogonMiddlewareTestCase.SERVICE_PORTNUM) 
     138 
    131139    def test01PasteFixtureInvalidCredentials(self): 
    132140        username = '_test' 
     
    160168        print response 
    161169         
    162     def test03Urllib2Client(self): 
    163         # Thread separate Paster based service  
    164         self.addService(app=self.wsgiapp,  
    165                         port=MyProxyLogonMiddlewareTestCase.SERVICE_PORTNUM) 
    166          
    167         username = self.username 
    168         if self.password is None: 
    169             from getpass import getpass 
    170             password = getpass('test03Urllib2Client: MyProxy pass-phrase for ' 
    171                                '%r: ' % username) 
     170    def test03Urllib2ClientGET(self): 
     171         
     172        username = self.username 
     173        if self.password is None: 
     174            from getpass import getpass 
     175            password = getpass('test03Urllib2ClientGET: MyProxy pass-phrase ' 
     176                               'for %r: ' % username) 
    172177        else: 
    173178            password = self.password 
     
    186191        print (response) 
    187192         
    188     def test04WGetClient(self): 
     193    def test04Urllib2ClientPOST(self): 
     194         
     195        username = self.username 
     196        if self.password is None: 
     197            from getpass import getpass 
     198            password = getpass('test04Urllib2ClientPOST: MyProxy pass-phrase ' 
     199                               'for %r: ' % username) 
     200        else: 
     201            password = self.password 
     202 
     203        url = 'http://localhost:%d/test_200' % \ 
     204            MyProxyLogonMiddlewareTestCase.SERVICE_PORTNUM 
     205             
     206        req = urllib2.Request(url) 
     207        base64String = base64.encodestring('%s:%s' % (username, password))[:-1] 
     208        authHeader =  "Basic %s" % base64String 
     209        req.add_header("Authorization", authHeader) 
     210         
     211        # Create key pair 
     212        keys = RSA.gen_key(nBitsForKey, m2.RSA_F4) 
     213        certReq = X509.Request() 
     214         
     215        # Create public key object 
     216        pubKey = EVP.PKey() 
     217        pubKey.assign_rsa(keys) 
     218         
     219        # Add the public key to the request 
     220        certReq.set_version(0) 
     221        certReq.set_pubkey(pubKey) 
     222         
     223        x509Name = X509.X509_Name()                         
     224        certReq.set_subject_name(x509Name) 
     225         
     226        certReq.sign(pubKey, messageDigest) 
     227 
     228        pemCertReq = certReq.as_pem() 
     229        
     230        handle = urllib2.urlopen(req, data=pemCertReq) 
     231         
     232        response = handle.read() 
     233        print (response) 
     234         
     235    def test05WGetClient(self): 
    189236        uri = ('http://localhost:%d/test_200' %  
    190237                  MyProxyLogonMiddlewareTestCase.SERVICE_PORTNUM) 
  • TI12-security/trunk/python/ndg_security_test/setup.py

    r6134 r6202  
    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 
     31    install_requires =      'PyOpenSSL', # Required for paster to run under SSL 
    3232    packages =                      find_packages(), 
    3333    namespace_packages =        ['ndg', 'ndg.security'], 
Note: See TracChangeset for help on using the changeset viewer.