1 | """NDG Security |
---|
2 | |
---|
3 | Client interface to Session Manager for WSGI based applications |
---|
4 | |
---|
5 | NERC Data Grid Project |
---|
6 | |
---|
7 | This software may be distributed under the terms of the Q Public License, |
---|
8 | version 1.0 or later. |
---|
9 | """ |
---|
10 | __author__ = "P J Kershaw" |
---|
11 | __date__ = "27/11/08" |
---|
12 | __copyright__ = "(C) 2008 STFC & NERC" |
---|
13 | __contact__ = "Philip.Kershaw@stfc.ac.uk" |
---|
14 | __revision__ = "$Id$" |
---|
15 | |
---|
16 | import logging |
---|
17 | log = logging.getLogger(__name__) |
---|
18 | |
---|
19 | import sys |
---|
20 | import os |
---|
21 | |
---|
22 | from ndg.security.server.wsgi.utils.attributeauthorityclient import \ |
---|
23 | WSGIAttributeAuthorityClient |
---|
24 | |
---|
25 | # Session Manager Authentication interface ... |
---|
26 | from ndg.security.server.sessionmanager import AuthNServiceError, \ |
---|
27 | AuthNServiceInvalidCredentials, AuthNServiceRetrieveError, \ |
---|
28 | AuthNServiceInitError, AuthNServiceConfigError |
---|
29 | |
---|
30 | # Session Manager SOAP client interface |
---|
31 | from ndg.security.common.sessionmanager import SessionManagerClient |
---|
32 | |
---|
33 | # Import exception types from Session Manager and Session Manager client to |
---|
34 | # give caller some capability to trap errors |
---|
35 | # Session Manager server side exceptions ... |
---|
36 | from ndg.security.server.sessionmanager import SessionManagerError, \ |
---|
37 | UserSessionExpired, UserSessionX509CertNotBeforeTimeError, \ |
---|
38 | InvalidUserSession, CredentialWalletAttributeRequestDenied |
---|
39 | |
---|
40 | from ndg.security.server.sessionmanager import SessionNotFound as \ |
---|
41 | _SrvSessionNotFound |
---|
42 | |
---|
43 | # ... and client side exceptions ... |
---|
44 | from ndg.security.common.sessionmanager import SessionNotFound as \ |
---|
45 | _ClntSessionNotFound |
---|
46 | |
---|
47 | from ndg.security.common.sessionmanager import SessionExpired as \ |
---|
48 | _ClntSessionExpired |
---|
49 | |
---|
50 | from ndg.security.common.sessionmanager import InvalidSession as \ |
---|
51 | _ClntInvalidSession |
---|
52 | |
---|
53 | from ndg.security.common.sessionmanager import AttributeRequestDenied as \ |
---|
54 | _ClntAttributeRequestDenied |
---|
55 | |
---|
56 | from ndg.security.common.sessionmanager import InvalidSessionManagerClientCtx,\ |
---|
57 | SessionManagerClientError, SessionCertTimeError |
---|
58 | |
---|
59 | # Combine Session not found exception classes as raised from server and |
---|
60 | # client side to enable convenient exception handling by a client to this |
---|
61 | # class. e.g. a call to WSGISessionManager.connect without the need to know |
---|
62 | # whether the wrapper is calling a remote service over the SOAP interface or |
---|
63 | # the service locally via a reference a Session Manager in environ: |
---|
64 | # |
---|
65 | # try: |
---|
66 | # wsgiClnt.connect(username, passphrase=p) |
---|
67 | # except SessionNotFound, e: |
---|
68 | # # do something |
---|
69 | # raise |
---|
70 | # |
---|
71 | # Rather than having to do: |
---|
72 | # |
---|
73 | # try: |
---|
74 | # wsgiClnt.connect(username, passphrase=p) |
---|
75 | # except (ndg.security.server.sessionmanager.SessionNotFound, |
---|
76 | # ndg.security.common.sessionmanager.SessionNotFound), e: |
---|
77 | # # do something |
---|
78 | # raise |
---|
79 | SessionNotFound = (_SrvSessionNotFound, _ClntSessionNotFound) |
---|
80 | |
---|
81 | # Combine client and server session not before time error exceptions to |
---|
82 | # enable easier exception handling for a WSGISessionManagerClient caller. |
---|
83 | # See SessionNotFound.__doc__ for more details of reasoning |
---|
84 | SessionNotBeforeTimeError = (UserSessionX509CertNotBeforeTimeError, |
---|
85 | SessionCertTimeError) |
---|
86 | |
---|
87 | # Combine client and server session expired exceptions to enable easier |
---|
88 | # exception handling for a WSGISessionManagerClient caller. See |
---|
89 | # SessionNotFound.__doc__ for more details of reasoning |
---|
90 | SessionExpired = (UserSessionExpired, _ClntSessionExpired) |
---|
91 | |
---|
92 | # Combine client and server invalid session exceptions to enable easier |
---|
93 | # exception handling for a WSGISessionManagerClient caller. See |
---|
94 | # SessionNotFound.__doc__ for more details of reasoning""" |
---|
95 | InvalidSession = (InvalidUserSession, _ClntInvalidSession) |
---|
96 | |
---|
97 | # Combine client and server invalid session exceptions to enable easier |
---|
98 | # exception handling for a WSGISessionManagerClient caller. See |
---|
99 | # SessionNotFound.__doc__ for more details of reasoning |
---|
100 | AttributeRequestDenied = (CredentialWalletAttributeRequestDenied, |
---|
101 | _ClntAttributeRequestDenied) |
---|
102 | |
---|
103 | # End of server/client side exception combinations |
---|
104 | |
---|
105 | |
---|
106 | class WSGISessionManagerClientError(Exception): |
---|
107 | """Base class exception for WSGI Session Manager client errors""" |
---|
108 | |
---|
109 | class WSGISessionManagerClientConfigError(WSGISessionManagerClientError): |
---|
110 | """Configuration error for WSGI Session Manager Client""" |
---|
111 | |
---|
112 | class WSGISessionManagerClient(object): |
---|
113 | """Client interface to Session Manager for WSGI based applications |
---|
114 | |
---|
115 | This class wraps the SOAP based web service client and alternate access to |
---|
116 | a Session Manager instance in the same code stack available via an environ |
---|
117 | keyword |
---|
118 | """ |
---|
119 | environKey = "ndg.security.server.wsgi.sessionManagerFilter" |
---|
120 | |
---|
121 | _refInEnviron = lambda self: self._environKey in self._environ |
---|
122 | |
---|
123 | # Define as property for convenient call syntax |
---|
124 | refInEnviron = property(fget=_refInEnviron, |
---|
125 | doc="return True if a Session Manager instance is " |
---|
126 | "available in WSGI environ") |
---|
127 | |
---|
128 | _getRef = lambda self:self._environ[self._environKey].serviceSOAPBinding.sm |
---|
129 | ref = property(fget=_getRef, doc="Session Manager local instance") |
---|
130 | |
---|
131 | |
---|
132 | def __init__(self, environKey=None, environ={}, **soapClientKw): |
---|
133 | |
---|
134 | log.debug("WSGISessionManagerClient.__init__ ...") |
---|
135 | |
---|
136 | self._environKey = environKey or WSGISessionManagerClient.environKey |
---|
137 | |
---|
138 | # Standard WSGI environment dict |
---|
139 | self._environ = environ |
---|
140 | |
---|
141 | if soapClientKw.get('uri'): |
---|
142 | self._soapClient = SessionManagerClient(**soapClientKw) |
---|
143 | else: |
---|
144 | self._soapClient = None |
---|
145 | |
---|
146 | def _setEnviron(self, environ): |
---|
147 | if not isinstance(environ, dict): |
---|
148 | raise TypeError("Expecting dict type for 'environ' property") |
---|
149 | self._environ = environ |
---|
150 | |
---|
151 | def _getEnviron(self, environ): |
---|
152 | return self._environ |
---|
153 | |
---|
154 | environ = property(fget=_getEnviron, |
---|
155 | fset=_setEnviron, |
---|
156 | doc="WSGI environ dictionary") |
---|
157 | |
---|
158 | def connect(self, username, **kw): |
---|
159 | """Request a new user session from the Session Manager |
---|
160 | |
---|
161 | @type username: string |
---|
162 | @param username: the username of the user to connect |
---|
163 | """ |
---|
164 | |
---|
165 | if self.refInEnviron: |
---|
166 | log.debug("Connecting to local Session Manager instance") |
---|
167 | if 'username' in kw: |
---|
168 | raise TypeError("connect() got an unexpected keyword argument " |
---|
169 | "'username'") |
---|
170 | |
---|
171 | # Connect to local instance |
---|
172 | res = self.ref.connect(username=username, **kw) |
---|
173 | else: |
---|
174 | log.debug("Connecting to remote Session Manager service") |
---|
175 | |
---|
176 | # Filter out keywords which apply to a Session Manager local |
---|
177 | # instance call |
---|
178 | kw.pop('userX509Cert', None) |
---|
179 | |
---|
180 | # Make connection to remote service |
---|
181 | res = self._soapClient.connect(username, **kw) |
---|
182 | |
---|
183 | # Convert from unicode because unicode causes problems with |
---|
184 | # M2Crypto private key load |
---|
185 | res = tuple([isinstance(i,unicode) and str(i) or i for i in res]) |
---|
186 | |
---|
187 | return res |
---|
188 | |
---|
189 | |
---|
190 | def disconnect(self, **kw): |
---|
191 | """Delete an existing user session from the Session Manager |
---|
192 | |
---|
193 | @type **kw: dict |
---|
194 | @param **kw: disconnect keywords applicable to |
---|
195 | ndg.security.server.sessionmanager.SessionManager.getSessionStatus and |
---|
196 | ndg.security.common.sessionmanager.SessionManagerClient.getSessionStatus |
---|
197 | the SOAP client""" |
---|
198 | |
---|
199 | # Modify keywords according to correct interface for server side / |
---|
200 | # SOAP client |
---|
201 | if self.refInEnviron: |
---|
202 | if 'userDN' in kw: |
---|
203 | log.warning('Removing keyword "userDN": this is not supported ' |
---|
204 | 'for calls to ndg.security.server.sessionmanager.' |
---|
205 | 'SessionManager.deleteUserSession') |
---|
206 | kw.pop('userX509Cert', None) |
---|
207 | |
---|
208 | self.ref.deleteUserSession(**kw) |
---|
209 | else: |
---|
210 | if 'userX509Cert' in kw: |
---|
211 | kw['userDN'] = kw.pop('userX509Cert').dn |
---|
212 | |
---|
213 | self._soapClient.disconnect(**kw) |
---|
214 | |
---|
215 | |
---|
216 | def getSessionStatus(self, **kw): |
---|
217 | """Check for the existence of a session with a given |
---|
218 | session ID / user certificate Distinguished Name |
---|
219 | |
---|
220 | @type **kw: dict |
---|
221 | @param **kw: disconnect keywords applicable to |
---|
222 | ndg.security.server.sessionmanager.SessionManager.getSessionStatus and |
---|
223 | ndg.security.common.sessionmanager.SessionManagerClient.getSessionStatus |
---|
224 | the SOAP client""" |
---|
225 | |
---|
226 | if self.refInEnviron: |
---|
227 | return self.ref.getSessionStatus(**kw) |
---|
228 | else: |
---|
229 | return self._soapClient.getSessionStatus(**kw) |
---|
230 | |
---|
231 | |
---|
232 | |
---|
233 | def getAttCert(self, **kw): |
---|
234 | """Request NDG Session Manager to retrieve an Attribute |
---|
235 | Certificate from the given Attribute Authority and cache it in the |
---|
236 | user's credential wallet held by the session manager. |
---|
237 | |
---|
238 | @type **kw: dict |
---|
239 | @param **kw: disconnect keywords applicable to |
---|
240 | ndg.security.server.sessionmanager.SessionManager.getAttCert and |
---|
241 | ndg.security.common.sessionmanager.SessionManagerClient.getAttCert |
---|
242 | the SOAP client |
---|
243 | """ |
---|
244 | |
---|
245 | if self.refInEnviron: |
---|
246 | # Connect to local instance of Session Manager - next check for |
---|
247 | # an Attribute Authority URI or instance running locally |
---|
248 | if kw.get('attributeAuthorityURI') is None and \ |
---|
249 | kw.get('attributeAuthority') is None: |
---|
250 | wsgiAttributeAuthorityClient = WSGIAttributeAuthorityClient( |
---|
251 | environ=self._environ) |
---|
252 | |
---|
253 | if wsgiAttributeAuthorityClient.refInEnviron: |
---|
254 | kw['attributeAuthority'] = wsgiAttributeAuthorityClient.ref |
---|
255 | else: |
---|
256 | raise WSGISessionManagerClientConfigError( |
---|
257 | "No Attribute Authority URI or server object has been " |
---|
258 | "set and no reference is available in environ") |
---|
259 | |
---|
260 | return self.ref.getAttCert(**kw) |
---|
261 | else: |
---|
262 | # Filter out keywords which apply to a Session Manager local |
---|
263 | # instance call |
---|
264 | if 'username' in kw: |
---|
265 | kw.pop('username') |
---|
266 | log.warning('Trying call via SOAP interface: ' |
---|
267 | 'removing the "username" keyword ' |
---|
268 | 'ndg.security.common.sessionmanager.' |
---|
269 | 'SessionManagerClient.getAttCert doesn\'t support ' |
---|
270 | 'this keyword') |
---|
271 | |
---|
272 | if 'refreshAttCert' in kw: |
---|
273 | kw.pop('refreshAttCert') |
---|
274 | log.warning('Trying call via SOAP interface: ' |
---|
275 | 'removing the "refreshAttCert" keyword ' |
---|
276 | 'ndg.security.common.sessionmanager.' |
---|
277 | 'SessionManagerClient.getAttCert doesn\'t support ' |
---|
278 | 'this keyword') |
---|
279 | |
---|
280 | if 'attCertRefreshElapse' in kw: |
---|
281 | kw.pop('attCertRefreshElapse') |
---|
282 | log.warning('Trying call via SOAP interface: ' |
---|
283 | 'removing the "attCertRefreshElapse" keyword ' |
---|
284 | 'ndg.security.common.sessionmanager.' |
---|
285 | 'SessionManagerClient.getAttCert doesn\'t support ' |
---|
286 | 'this keyword') |
---|
287 | |
---|
288 | return self._soapClient.getAttCert(**kw) |
---|