1 | import sys # tracefile config param may be set to e.g. sys.stderr |
---|
2 | import urllib2 |
---|
3 | import socket |
---|
4 | |
---|
5 | from ows_server.lib.base import * |
---|
6 | from pylons import request |
---|
7 | import logging |
---|
8 | log = logging.getLogger(__name__) |
---|
9 | |
---|
10 | from ows_common.exception_report import OwsError |
---|
11 | from ows_server.lib.security_util import SecuritySession |
---|
12 | |
---|
13 | try: |
---|
14 | from ndg.security.common.SessionMgr import SessionMgrClient, SessionNotFound,\ |
---|
15 | SessionCertTimeError, SessionExpired, InvalidSession, \ |
---|
16 | AttributeRequestDenied |
---|
17 | |
---|
18 | def HandleSecurity(*args): |
---|
19 | return SecurityHandler(*args)() |
---|
20 | except: |
---|
21 | def HandleSecurity(*args): |
---|
22 | return 0,'Access Control System Not Installed' |
---|
23 | |
---|
24 | class URLCannotBeOpened(Exception): |
---|
25 | """Raise from canURLBeOpened SecurityHandler class method |
---|
26 | if URL is invalid - this method is used to check the AA |
---|
27 | service""" |
---|
28 | |
---|
29 | class SecurityHandler(object): |
---|
30 | """Make access control decision based on CSML constraint and user security |
---|
31 | token""" |
---|
32 | |
---|
33 | AccessAllowedMsg = "Access Allowed" |
---|
34 | InvalidAttributeCertificate = \ |
---|
35 | "The certificate containing your authorisation roles is invalid" |
---|
36 | NotLoggedInMsg = 'Not Logged in' |
---|
37 | SessionExpiredMsg = 'Session has expired. Please re-login' |
---|
38 | InvalidSessionMsg = 'Session is invalid. Please try re-login' |
---|
39 | InvalidSecurityCondition = 'Invalid Security Condition' |
---|
40 | |
---|
41 | def __init__(self, uri, securityElement, securityTokens): |
---|
42 | """Initialise settings for WS-Security and SSL for SOAP |
---|
43 | call to Session Manager |
---|
44 | |
---|
45 | @type uri: string |
---|
46 | @param uri: URI corresponding to data granule ID |
---|
47 | |
---|
48 | @type securityElement: ElementTree Element |
---|
49 | @param securityElement: MOLES security constraint containing role and |
---|
50 | Attribute Authority URI. In xml, could look like: |
---|
51 | <moles:effect>allow</moles:effect> |
---|
52 | <moles:simpleCondition> |
---|
53 | <moles:dgAttributeAuthority>https://glue.badc.rl.ac.uk/AttributeAuthority</moles:dgAttributeAuthority> |
---|
54 | <moles:attrauthRole>coapec</moles:attrauthRole> |
---|
55 | </moles:simpleCondition> |
---|
56 | NB: xmlns:moles="http://ndg.nerc.ac.uk/moles |
---|
57 | |
---|
58 | @type: pylons.session |
---|
59 | @param securityTokens: dict-like session object containing security |
---|
60 | tokens""" |
---|
61 | |
---|
62 | self.uri = uri |
---|
63 | self.securityElement = securityElement |
---|
64 | self.securityTokens = securityTokens |
---|
65 | |
---|
66 | |
---|
67 | def __call__(self, **kw): |
---|
68 | """Convenience wrapper for checkAccess""" |
---|
69 | return self.checkAccess(**kw) |
---|
70 | |
---|
71 | |
---|
72 | def checkAccess(self, |
---|
73 | uri=None, |
---|
74 | securityElement=None, |
---|
75 | securityTokens=None): |
---|
76 | """Make an access control decision based on whether the user is |
---|
77 | authenticated and has the required roles |
---|
78 | |
---|
79 | @type uri: string |
---|
80 | @param uri: URI corresponding to data granule ID |
---|
81 | |
---|
82 | @type: ElementTree Element |
---|
83 | @param securityElement: MOES security constraint containing role and |
---|
84 | Attribute Authority URI. In xml, could look like: |
---|
85 | <moles:effect>allow</moles:effect> |
---|
86 | <moles:simpleCondition> |
---|
87 | <moles:dgAttributeAuthority>https://glue.badc.rl.ac.uk/AttributeAuthority</moles:dgAttributeAuthority> |
---|
88 | <moles:attrauthRole>coapec</moles:attrauthRole> |
---|
89 | </moles:simpleCondition> |
---|
90 | NB: xmlns:moles="http://ndg.nerc.ac.uk/moles" |
---|
91 | |
---|
92 | @type: pylons.session |
---|
93 | @param securityTokens: dict-like session object containing security |
---|
94 | tokens. Resets equivalent object attribute.""" |
---|
95 | |
---|
96 | # tokens and element may be set from __init__ or as args to this |
---|
97 | # method. If the latter copy them into self |
---|
98 | if uri: |
---|
99 | self.uri = uri |
---|
100 | |
---|
101 | if securityTokens: |
---|
102 | self.securityTokens = securityTokens |
---|
103 | |
---|
104 | if securityElement: |
---|
105 | self.securityElement=securityElement |
---|
106 | |
---|
107 | # Check self.securityTokens - if not set then the user mustn't be |
---|
108 | # logged in. This situation is possible if a user has been denied |
---|
109 | # access to data and then tried to logout - after log out they are |
---|
110 | # redirected back to the page where they tried accessing data but this |
---|
111 | # time they will have no security credential set |
---|
112 | if not self.securityTokens: |
---|
113 | # Try to recover and do something sensible |
---|
114 | # |
---|
115 | # TODO: this adds insult to injury if the person has just been |
---|
116 | # denied access to data. Instead do a redirect back to the |
---|
117 | # discovery page? |
---|
118 | # P J Kershaw 10/08/07 |
---|
119 | log.info("Exiting from Gatekeeper: user is not logged in") |
---|
120 | return False, self.__class__.NotLoggedInMsg |
---|
121 | |
---|
122 | xpathr='{http://ndg.nerc.ac.uk/moles}simpleCondition/{http://ndg.nerc.ac.uk/moles}attrauthRole' |
---|
123 | xpathaa='{http://ndg.nerc.ac.uk/moles}simpleCondition/{http://ndg.nerc.ac.uk/moles}dgAttributeAuthority' |
---|
124 | roleE,aaE=self.securityElement.find(xpathr),self.securityElement.find(xpathaa) |
---|
125 | if roleE is None: |
---|
126 | log.error("Gatekeeper: role not found in dataset element: %s" % \ |
---|
127 | self.securityElement) |
---|
128 | return False, self.__class__.InvalidSecurityCondition |
---|
129 | |
---|
130 | self.reqRole=roleE.text |
---|
131 | |
---|
132 | # Check Attribute Authority address |
---|
133 | try: |
---|
134 | SecurityHandler.urlCanBeOpened(aaE.text) |
---|
135 | except (URLCannotBeOpened, AttributeError): |
---|
136 | # Catch situation where either Attribute Authority address in the |
---|
137 | # data invalid or none was set. In this situation verify |
---|
138 | # against the Attribute Authority set in the config |
---|
139 | log.info('Gatekeeper: Attribute Authority address is invalid ' + \ |
---|
140 | 'in data "%s" - defaulting to config file setting' % \ |
---|
141 | self.securityElement) |
---|
142 | self.reqAAURI = g.securityCfg.aaURI |
---|
143 | |
---|
144 | # Create Session Manager client |
---|
145 | self.smClnt = SessionMgrClient(uri=self.securityTokens['h'], |
---|
146 | sslCACertFilePathList=g.securityCfg.sslCACertFilePathList, |
---|
147 | sslPeerCertCN=g.securityCfg.sslPeerCertCN, |
---|
148 | signingCertFilePath=g.securityCfg.wssCertFilePath, |
---|
149 | signingPriKeyFilePath=g.securityCfg.wssPriKeyFilePath, |
---|
150 | signingPriKeyPwd=g.securityCfg.wssPriKeyPwd, |
---|
151 | caCertFilePathList=g.securityCfg.wssCACertFilePathList, |
---|
152 | tracefile=g.securityCfg.tracefile) |
---|
153 | |
---|
154 | return self.__checkAttCert() |
---|
155 | |
---|
156 | |
---|
157 | |
---|
158 | def __checkAttCert(self): |
---|
159 | """Check to see if the Session Manager can deliver an Attribute |
---|
160 | Certificate with the required role to gain access to the resource |
---|
161 | in question""" |
---|
162 | |
---|
163 | try: |
---|
164 | # Make request for attribute certificate |
---|
165 | attCert = self.smClnt.getAttCert(attAuthorityURI=self.reqAAURI, |
---|
166 | sessID=self.securityTokens['sid'], |
---|
167 | reqRole=self.reqRole) |
---|
168 | except AttributeRequestDenied, e: |
---|
169 | log.info(\ |
---|
170 | "Gatekeeper - request for attribute certificate denied: %s"%e) |
---|
171 | return False, str(e) |
---|
172 | |
---|
173 | except SessionNotFound, e: |
---|
174 | # Clear the security details from the session object |
---|
175 | SecuritySession.delete() |
---|
176 | log.info("Gatekeeper - no session found: %s" % e) |
---|
177 | return False, self.__class__.NotLoggedInMsg |
---|
178 | |
---|
179 | except SessionExpired, e: |
---|
180 | # Clear the security details from the session object |
---|
181 | SecuritySession.delete() |
---|
182 | log.info("Gatekeeper - session expired: %s" % e) |
---|
183 | return False, self.__class__.SessionExpiredMsg |
---|
184 | |
---|
185 | except SessionCertTimeError, e: |
---|
186 | # Clear the security details from the session object |
---|
187 | SecuritySession.delete() |
---|
188 | log.info("Gatekeeper - session cert. time error: %s" % e) |
---|
189 | return False, self.__class__.InvalidSessionMsg |
---|
190 | |
---|
191 | except InvalidSession, e: |
---|
192 | SecuritySession.delete() |
---|
193 | log.info("Gatekeeper - invalid user session: %s" % e) |
---|
194 | return False, self.__class__.InvalidSessionMsg |
---|
195 | |
---|
196 | except Exception, e: |
---|
197 | raise OwsError, "Gatekeeper request for attribute certificate: "+\ |
---|
198 | str(e) |
---|
199 | |
---|
200 | # Check attribute certificate is valid |
---|
201 | attCert.certFilePathList = g.securityCfg.acCACertFilePathList |
---|
202 | attCert.isValid(raiseExcep=True) |
---|
203 | |
---|
204 | # Check it's issuer is as expected |
---|
205 | if attCert.issuer != g.securityCfg.acIssuer: |
---|
206 | log.info('Gatekeeper - access denied: Attribute Certificate ' + \ |
---|
207 | 'issuer DN, "%s" ' % attCert.issuer + \ |
---|
208 | 'must match this data provider\'s Attribute Authority ' + \ |
---|
209 | 'DN: "%s"' % g.securityCfg.acIssuer) |
---|
210 | return False, self.__class__.InvalidAttributeCertificate |
---|
211 | |
---|
212 | log.info('Gatekeeper - access granted for user "%s" '%attCert.userId+\ |
---|
213 | 'to "%s" secured with role "%s" ' % (self.uri,self.reqRole)+\ |
---|
214 | 'using attribute certificate:\n\n%s' % attCert) |
---|
215 | |
---|
216 | return True, self.__class__.AccessAllowedMsg |
---|
217 | |
---|
218 | @classmethod |
---|
219 | def urlCanBeOpened(cls, url, timeout=5, raiseExcep=True): |
---|
220 | """Check url can be opened - adapted from |
---|
221 | http://mail.python.org/pipermail/python-list/2004-October/289601.html |
---|
222 | """ |
---|
223 | |
---|
224 | found = False |
---|
225 | defTimeOut = socket.getdefaulttimeout() |
---|
226 | try: |
---|
227 | socket.setdefaulttimeout(timeout) |
---|
228 | |
---|
229 | try: |
---|
230 | urllib2.urlopen(url) |
---|
231 | except (urllib2.HTTPError, urllib2.URLError, |
---|
232 | socket.error, socket.sslerror): |
---|
233 | if raiseExcep: |
---|
234 | raise URLCannotBeOpened |
---|
235 | |
---|
236 | found = True |
---|
237 | |
---|
238 | finally: |
---|
239 | socket.setdefaulttimeout(defTimeOut) |
---|
240 | |
---|
241 | return found |
---|