source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/authz/pdp/moles.py @ 3790

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/authz/pdp/moles.py@3790
Revision 3790, 13.0 KB checked in by pjkersha, 13 years ago (diff)

Fixes to PEP and PDP for BADC deployment

Line 
1"""NDG Policy Decision Point for NDG Browse - access constraints for a
2resource are determined from MOLES access constraints in the data
3
4NERC Data Grid Project
5"""
6__author__ = "P J Kershaw"
7__date__ = "04/04/08"
8__copyright__ = "(C) 2008 STFC & NERC"
9__contact__ = "P.J.Kershaw@rl.ac.uk"
10__license__ = \
11"""This software may be distributed under the terms of the Q Public
12License, version 1.0 or later."""
13__contact__ = "P.J.Kershaw@rl.ac.uk"
14__revision__ = "$Id:gatekeeper.py 3079 2007-11-30 09:39:46Z pjkersha $"
15
16import logging
17log = logging.getLogger(__name__)
18
19import sys # tracefile config param may be set to e.g. sys.stderr
20import urllib2
21import socket
22from ConfigParser import SafeConfigParser
23
24# For parsing of properties file
25from os.path import expandvars as expVars
26
27from ndg.security.common.authz.pdp import PDPInterface, PDPError, \
28    PDPUserAccessDenied, PDPUserNotLoggedIn, PDPMissingResourceConstraints
29   
30from ndg.security.common.SessionMgr import SessionMgrClient, SessionNotFound,\
31    SessionCertTimeError, SessionExpired, InvalidSession, \
32    AttributeRequestDenied                   
33   
34from ndg.security.common.X509 import X500DN               
35
36class InvalidAttributeCertificate(PDPError):
37    "The certificate containing authorisation roles is invalid"
38    def __init__(self, msg=None):
39        PDPError.__init__(self, msg or InvalidAttributeCertificate.__doc__)
40   
41class SessionExpiredMsg(PDPError):
42    'Session has expired.  Please re-login'
43    def __init__(self, msg=None):
44        PDPError.__init__(self, msg or SessionExpiredMsg.__doc__)
45
46class InvalidSessionMsg(PDPError):
47    'Session is invalid.  Please try re-login'
48    def __init__(self, msg=None):
49        PDPError.__init__(self, msg or InvalidSessionMsg.__doc__)
50
51class InitSessionCtxError(PDPError):
52    'A problem occured initialising a session connection'
53    def __init__(self, msg=None):
54        PDPError.__init__(self, msg or InitSessionCtxError.__doc__)
55
56class AttributeCertificateRequestError(PDPError):
57    'A problem occured requesting a certificate containing authorisation roles'
58    def __init__(self, msg=None):
59        PDPError.__init__(self,msg or AttributeCertificateRequestError.__doc__)
60
61class URLCannotBeOpened(PDPError):
62    """Raise from canURLBeOpened PullModelHandler class method
63    if URL is invalid - this method is used to check the AA
64    service"""
65
66
67class MolesPDP(PDPInterface):
68    """Make access control decision based on a MOLES access constraint
69    (applies to CSML too) and user security token"""
70
71    molesXPathQueryPfx = \
72'{http://ndg.nerc.ac.uk/moles}simpleCondition/{http://ndg.nerc.ac.uk/moles}'
73    roleXPathQuery = molesXPathQueryPfx + 'attrauthRole'
74    roleXPathQuery = molesXPathQueryPfx + 'dgAttributeAuthority'
75
76    defParam = {'aaURI': '',
77                'sslCACertFilePathList': [],
78                'tracefile': '',
79                'acCACertFilePathList': [], 
80                'acIssuer': ''}
81           
82   
83    def __init__(self, cfgFilePath=None, **cfgKw):
84        """Initialise settings for WS-Security and SSL for SOAP
85        call to Session Manager
86       
87        @type uri: string
88        @param uri: URI corresponding to data granule ID
89       
90        @type securityElement: ElementTree Element
91        @param securityElement: MOLES security constraint containing role and
92        Attribute Authority URI. In xml, could look like:
93        <moles:effect>allow</moles:effect>
94            <moles:simpleCondition>
95            <moles:dgAttributeAuthority>https://glue.badc.rl.ac.uk/AttributeAuthority</moles:dgAttributeAuthority>
96            <moles:attrauthRole>coapec</moles:attrauthRole>
97        </moles:simpleCondition>
98        NB: xmlns:moles="http://ndg.nerc.ac.uk/moles
99        """
100       
101        self.cfgFilePath = cfgFilePath
102        self.resrcURI = None
103        self.securityElement = None
104        self.userHandle = None
105       
106        # Set from config file
107        if cfgFilePath:
108            self._readConfig()
109           
110        # Separate keywords into PDP and WS-Security specific items
111        paramNames = cfgKw.keys()
112        for paramName in paramNames:
113            if paramName in MolesPDP.defParam:
114                # Keywords are deleted as they are set
115                setattr(self, paramName, cfgKw.pop('paramName'))
116               
117        # Remaining keys must be for WS-Security config
118        self.wssCfg = cfgKw   
119
120           
121    def _readConfig(self, section='DEFAULT'):
122        '''Read PDP configuration file'''
123        cfg = SafeConfigParser()
124        cfg.read(self.cfgFilePath)
125       
126        # Copy directly into attribute of this object
127        for paramName, paramVal in MolesPDP.defParam.items():
128            if isinstance(paramVal, list):
129                paramListVal = expVars(cfg.get(section, paramName)).split()
130                setattr(self, paramName, paramListVal)
131            else:
132                setattr(self, paramName, expVars(cfg.get(section, paramName)))           
133
134
135    def accessPermitted(self, resrcHandle, userHandle, accessType=None):
136        """Make an access control decision based on whether the user is
137        authenticated and has the required roles
138       
139        @type resrcHandle: dict
140        @param resrcHandle: dict 'uri' = resource URI, 'securityElement' =
141        ElementTree type MOLES security Element
142       
143        @type userHandle: dict
144        @param userHandle: dict with keys 'sid' = user session ID,
145        'h' = Session Manager URI
146       
147        @type accessType: -
148        @param accessType: not implemented - logs a warning if set
149       
150        @rtype: bool
151        @return: True if access permitted; False if denied or else raise
152        an Exception
153       
154        @type uri: string
155        @param uri: URI corresponding to data granule ID
156       
157        @type: ElementTree Element
158        @param securityElement: MOES security constraint containing role and
159        Attribute Authority URI. In xml, could look like:
160        <moles:effect>allow</moles:effect>
161            <moles:simpleCondition>
162            <moles:dgAttributeAuthority>https://glue.badc.rl.ac.uk/AttributeAuthority</moles:dgAttributeAuthority>
163            <moles:attrauthRole>coapec</moles:attrauthRole>
164        </moles:simpleCondition>
165        NB: xmlns:moles="http://ndg.nerc.ac.uk/moles"
166       
167        @type: pylons.session
168        @param userHandle: dict-like session object containing security
169        tokens.  Resets equivalent object attribute."""
170         
171        # Resource handle contains URI and ElementTree resource security
172        # element
173        try:
174            self.resrcURI = resrcHandle['uri']
175            self.securityElement = resrcHandle['securityElement'] 
176        except KeyError, e:
177            log.error("Resource handle missing key %s" % e)
178            raise PDPMissingResourceConstraints()
179       
180        # User handle contains 'h' = Session Manager URI and 'sid' user
181        # Session ID
182        try:
183            self.smURI = userHandle['h']
184            self.userSessID = userHandle['sid']
185        except KeyError, e:
186            log.error("User handle missing key %s" % e)
187            raise PDPUserNotLoggedIn()
188
189           
190        roleElem = self.securityElement.find(MolesPDP.roleXPathQuery)
191        if roleElem is None or not roleElem.text:
192            log.error("PDP: role not set in MOLES security " + \
193                      "constraints")
194            raise PDPMissingResourceConstraints()
195       
196        self.reqRole = roleElem.text
197
198        aaElem = self.securityElement.find(MolesPDP.aaXPathQuery)
199       
200        # Sanity check on Attribute Authority URI
201        if aaElem and aaElem.text:
202            aaURI = aaElem.text
203           
204            # Check Attribute Authority address
205            try:
206                MolesPDP.urlCanBeOpened(aaURI)
207            except URLCannotBeOpened, e:
208                # Catch situation where either Attribute Authority address in the
209                # data invalid or none was set.  In this situation verify
210                # against the Attribute Authority set in the config
211   
212                log.warning('PDP: MOLES security constraint ' + \
213                            'Attribute Authority address is invalid - ' + \
214                            'defaulting to config file setting: %s; ' % \
215                            self.aaURI + \
216                            'error message is: %s' % e)
217                aaURI = self.aaURI
218        else:
219            log.warning("PDP: Attribute Authority element not " + \
220                        "set in MOLES security constraints - defaulting " + \
221                        "to config file setting: %s" % self.aaURI)
222            aaURI = self.aaURI
223   
224        # Retrieve Attirbute Certificate from user's session held by
225        # Session Manager
226        attCert = self._pullUserSessionAttCert(aaURI)
227       
228        # Check its validity
229        self._checkAttCert(attCert)
230                   
231        log.info('PDP - access granted for user "%s" ' % \
232                 attCert.userId + \
233                 'to "%s" secured with role "%s" ' % \
234                 (self.resrcURI, self.reqRole) + \
235                 'using attribute certificate:\n\n%s' % attCert)
236           
237       
238    def _pullUserSessionAttCert(self, aaURI):
239        """Check to see if the Session Manager can deliver an Attribute
240        Certificate with the required role to gain access to the resource
241        in question
242       
243        @type aaURI: string
244        @param aaURI: address of Attribute Authority that the Session Manager
245        will call in order to request an AC on behalf of the user"""
246       
247        try:
248            # Create Session Manager client
249            self.smClnt = SessionMgrClient(uri=self.smURI,
250                            cfgFilePath=self.cfgFilePath,
251                            cfgFileSection='WS-Security',
252                            sslCACertFilePathList=self.sslCACertFilePathList,
253                            tracefile=self.tracefile,
254                            **self.wssCfg) 
255        except Exception, e:
256            log.error("PDP: creating Session Manager client: %s"%e)
257            raise InitSessionCtxError()
258       
259                 
260        try:
261            # Make request for attribute certificate
262            attCert = self.smClnt.getAttCert(attAuthorityURI=aaURI,
263                                             sessID=self.userSessID,
264                                             reqRole=self.reqRole)
265            return attCert
266       
267        except AttributeRequestDenied, e:
268            log.info(\
269            "PDP -request for attribute certificate denied: %s" % e)
270            raise PDPUserAccessDenied()
271       
272        except SessionNotFound, e:
273            log.info("PDP -no session found: %s" % e)
274            raise PDPUserNotLoggedIn()
275
276        except SessionExpired, e:
277            log.info("PDP -session expired: %s" % e)
278            raise InvalidSessionMsg()
279
280        except SessionCertTimeError, e:
281            log.info("PDP -session cert. time error: %s" % e)
282            raise InvalidSessionMsg()
283           
284        except InvalidSession, e:
285            log.info("PDP -invalid user session: %s" % e)
286            raise InvalidSessionMsg()
287
288        except Exception, e:
289            log.error("PDP request for attribute certificate: %s" % e)
290            raise AttributeCertificateRequestError()
291       
292
293    def _checkAttCert(self, attCert):
294        '''Check attribute certificate is valid
295       
296        @type attCert: ndg.security.common.AttCert.AttCert
297        @param attCert: attribute certificate to be check for validity'''
298        attCert.certFilePathList = self.acCACertFilePathList
299        try:
300            attCert.isValid(raiseExcep=True)
301        except Exception, e:
302            log.error("Attribute Certificate: %s" % e)
303            raise InvalidAttributeCertificate() 
304         
305        # Check it's issuer is as expected - Convert to X500DN to do equality
306        # test
307        acIssuerDN = X500DN(self.acIssuer)
308        if attCert.issuerDN != acIssuerDN:
309            log.info('PDP -access denied: Attribute Certificate ' + \
310                'issuer DN, "%s" ' % attCert.issuerDN + \
311                'must match this data provider\'s Attribute Authority ' + \
312                'DN: "%s"' % acIssuerDN)
313            raise InvalidAttributeCertificate()
314
315
316    @classmethod
317    def urlCanBeOpened(cls, url, timeout=5, raiseExcep=True):
318       """Check url can be opened - adapted from
319       http://mail.python.org/pipermail/python-list/2004-October/289601.html
320       """
321   
322       found = False
323       defTimeOut = socket.getdefaulttimeout()
324       try:
325           socket.setdefaulttimeout(timeout)
326
327           try:
328               urllib2.urlopen(url)
329           except (urllib2.HTTPError, urllib2.URLError,
330                   socket.error, socket.sslerror, AttributeError):
331               if raiseExcep:
332                   raise URLCannotBeOpened()
333           
334           found = True
335         
336       finally:
337           socket.setdefaulttimeout(defTimeOut)
338           
339       return found
340     
341   
342def makeDecision(resrcHandle, userHandle, accessType=None, **kw):
343    '''One call Wrapper interface to PDP'''
344    return MolesPDP(**kw)(resrcHandle, userHandle)
345
346 
Note: See TracBrowser for help on using the repository browser.