Changeset 6897
- Timestamp:
- 27/05/10 16:36:40 (11 years ago)
- Location:
- TI12-security/trunk/MyProxyServerUtils/myproxy/server
- Files:
-
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
TI12-security/trunk/MyProxyServerUtils/myproxy/server/test/httpbasicauth.ini
r6881 r6897 23 23 httpbasicauth.authnFuncEnvKeyName = HTTPBASICAUTH_FUNC 24 24 httpbasicauth.rePathMatchList = /auth 25 httpbasicauth.realm = test-realm -
TI12-security/trunk/MyProxyServerUtils/myproxy/server/test/myproxywsgi.ini
r6895 r6897 20 20 paste.app_factory = myproxy.server.wsgi.app:MyProxyLogonApp.app_factory 21 21 prefix = myproxy. 22 myproxy.httpbasicauth.realm = myproxy-realm 22 23 myproxy.logonFuncEnvKeyName = MYPROXY_LOGON_FUNC 23 24 myproxy.rePathMatchList = /logon -
TI12-security/trunk/MyProxyServerUtils/myproxy/server/test/test_httpbasicauth.py
r6881 r6897 18 18 19 19 from myproxy.server.wsgi.httpbasicauth import (HttpBasicAuthMiddleware, 20 HttpBasicAuth Unauthorized)20 HttpBasicAuthResponseException) 21 21 22 22 … … 85 85 if (username != self.__class__.USERNAME or 86 86 password != self.__class__.PASSWORD): 87 raise HttpBasicAuth Unauthorized()87 raise HttpBasicAuthResponseException("Invalid credentials") 88 88 89 89 environ['HTTPBASICAUTH_FUNC'] = authenticate -
TI12-security/trunk/MyProxyServerUtils/myproxy/server/test/test_myproxywsgi.cfg
r6895 r6897 13 13 # 14 14 [test01Logon] 15 username: pjk 15 username = https://ceda.ac.uk/openid/Philip.Kershaw 16 #username: pjk 16 17 #password = mypassword 17 18 uri = https://localhost:10443/logon -
TI12-security/trunk/MyProxyServerUtils/myproxy/server/test/test_myproxywsgi.py
r6892 r6897 1 1 #!/usr/bin/env python 2 """Unit tests for MyProxy WSGI Middleware classes and Application 2 """Unit tests for MyProxy WSGI Middleware classes and Application. These are 3 run using paste.fixture i.e. tests stubs to a web application server 3 4 """ 4 5 __author__ = "P J Kershaw" … … 90 91 91 92 def test01Logon(self): 93 # Test successful logon 92 94 username = self.cfg.get('test01Logon', 'username') 93 95 try: … … 105 107 print response 106 108 self.assert_(response) 107 109 110 def test02NoAuthorisationHeaderSet(self): 111 # Test failure with omission of HTTP Basic Auth header - a 401 result is 112 # expected. 113 114 # Create key pair and certificate request 115 keyPair, certReq = self._createRequestCreds() 116 response = self.app.post('/logon', certReq, status=401) 117 print response 118 self.assert_(response) 119 120 def test03NoCertificateRequestSent(self): 121 # Test with missing certificate request 122 123 username = self.cfg.get('test01Logon', 'username') 124 try: 125 password = self.cfg.get('test01Logon', 'password') 126 except NoOptionError: 127 password = getpass('test01Logon password: ') 128 129 base64String = base64.encodestring('%s:%s' % (username, password))[:-1] 130 authHeader = "Basic %s" % base64String 131 headers = {'Authorization': authHeader} 132 133 # Bad POST'ed content 134 response = self.app.post('/logon', 'x', headers=headers, status=400) 135 print response 136 self.assert_(response) 137 138 def test04GET(self): 139 # Test HTTP GET request - should be rejected - POST is expected 140 141 username = self.cfg.get('test01Logon', 'username') 142 try: 143 password = self.cfg.get('test01Logon', 'password') 144 except NoOptionError: 145 password = getpass('test01Logon password: ') 146 147 base64String = base64.encodestring('%s:%s' % (username, password))[:-1] 148 authHeader = "Basic %s" % base64String 149 headers = {'Authorization': authHeader} 150 151 response = self.app.get('/logon', headers=headers, status=405) 152 print response 153 self.assert_(response) 108 154 109 155 if __name__ == "__main__": -
TI12-security/trunk/MyProxyServerUtils/myproxy/server/test/test_myproxywsgi_with_paster.py
r6895 r6897 1 1 #!/usr/bin/env python 2 2 """Unit tests for MyProxy WSGI Middleware classes and Application testing them 3 with PAster web application server 3 with Paster web application server. The server is started from __init__ method 4 of the Test Case class and then called by the unit test methods. The unit 5 test methods themselves using a bash script myproxy-ws-logon.sh to query the 6 MyProxy web application. 4 7 """ 5 8 __author__ = "P J Kershaw" … … 9 12 __contact__ = "Philip.Kershaw@stfc.ac.uk" 10 13 __revision__ = '$Id$' 11 from os import path , waitpid14 from os import path 12 15 from getpass import getpass 13 from cStringIO import StringIO14 16 from ConfigParser import SafeConfigParser, NoOptionError 15 17 import subprocess -
TI12-security/trunk/MyProxyServerUtils/myproxy/server/wsgi/app.py
r6888 r6897 28 28 """ 29 29 PARAM_PREFIX = 'myproxy.logon.' 30 HTTPBASICAUTH_REALM_OPTNAME = 'httpbasicauth.realm' 30 31 31 32 @classmethod … … 46 47 logonFuncEnvironKeyName = app_conf.get(logonFuncEnvKeyNameOptName, 47 48 MyProxyClientMiddleware.LOGON_FUNC_ENV_KEYNAME) 48 49 # Mirror callback function setting in HTTP Basic Auth middleware so 50 # that it correctly picks up the authentication function 51 app_conf[prefix + HttpBasicAuthMiddleware.AUTHN_FUNC_ENV_KEYNAME_OPTNAME 52 ] = logonFuncEnvironKeyName 53 49 54 50 app = MyProxyLogonApp() 55 51 httpBasicAuthMWare = HttpBasicAuthMiddleware.filter_app_factory(app, … … 67 63 httpBasicAuthMWare.authnFuncEnvironKeyName = app.logonFuncEnvironKeyName 68 64 65 # Mirror callback function setting in HTTP Basic Auth middleware so 66 # that it correctly picks up the authentication function 67 realmOptName = prefix + cls.HTTPBASICAUTH_REALM_OPTNAME 68 httpBasicAuthMWare.realm = app_conf[realmOptName] 69 69 70 return app 70 71 -
TI12-security/trunk/MyProxyServerUtils/myproxy/server/wsgi/httpbasicauth.py
r6895 r6897 23 23 24 24 25 class HttpBasicAuthUnauthorized(HttpBasicAuthMiddlewareError): 26 """Raise from custom authentication interface in order to set HTTP 27 401 Unuathorized response""" 25 class HttpBasicAuthResponseException(HttpBasicAuthMiddlewareError): 26 """Exception class for use by the authentication function callback to 27 signal HTTP codes and messages back to HttpBasicAuthMiddleware. The code 28 can conceivably a non-error HTTP code such as 200 29 """ 30 def __init__(self, *arg, **kw): 31 """Extend Exception type to accommodate an extra HTTP response code 32 argument 33 """ 34 self.response = arg[0] 35 if len(arg) == 2: 36 argList = list(arg) 37 self.code = argList.pop() 38 arg = tuple(argList) 39 else: 40 self.code = httplib.UNAUTHORIZED 41 42 HttpBasicAuthMiddlewareError.__init__(self, *arg, **kw) 28 43 29 44 … … 33 48 AUTHN_FUNC_ENV_KEYNAME = ( 34 49 'myproxy.server.wsgi.httpbasicauth.HttpBasicAuthMiddleware.authenticate') 50 51 # Config file option names 35 52 AUTHN_FUNC_ENV_KEYNAME_OPTNAME = 'authnFuncEnvKeyName' 53 RE_PATH_MATCH_LIST_OPTNAME = 'rePathMatchList' 54 REALM_OPTNAME = 'realm' 55 36 56 PARAM_PREFIX = 'http.auth.basic.' 37 HTTP_HDR_FIELDNAME = 'basic' 57 58 # HTTP header request and response field parameters 59 AUTHENTICATE_HDR_FIELDNAME = 'WWW-Authenticate' 60 61 # For testing header content in start_response_wrapper 62 AUTHENTICATE_HDR_FIELDNAME_LOWER = AUTHENTICATE_HDR_FIELDNAME.lower() 63 64 AUTHN_SCHEME_HDR_FIELDNAME = 'Basic' 65 AUTHN_SCHEME_HDR_FIELDNAME_LOWER = AUTHN_SCHEME_HDR_FIELDNAME.lower() 66 38 67 FIELD_SEP = ':' 39 68 AUTHZ_ENV_KEYNAME = 'HTTP_AUTHORIZATION' 40 69 41 RE_PATH_MATCH_LIST_OPTNAME = 'rePathMatchList' 42 43 __slots__ = ('__rePathMatchList', '__authnFuncEnvironKeyName', '__app') 70 __slots__ = ( 71 '__rePathMatchList', 72 '__authnFuncEnvironKeyName', 73 '__realm', 74 '__app' 75 ) 44 76 45 77 def __init__(self, app): 46 78 self.__rePathMatchList = None 47 79 self.__authnFuncEnvironKeyName = None 80 self.__realm = None 48 81 self.__app = app 49 82 … … 122 155 "URI paths intercepted by this middleware") 123 156 157 def _getRealm(self): 158 return self.__realm 159 160 def _setRealm(self, value): 161 if not isinstance(value, basestring): 162 raise TypeError('Expecting string type for ' 163 '"realm"; got %r' % type(value)) 164 165 self.__realm = value 166 167 realm = property(fget=_getRealm, fset=_setRealm, 168 doc="HTTP Authentication realm to set in responses") 169 124 170 def _pathMatch(self, environ): 125 171 """Apply a list of regular expression matching patterns to the contents … … 155 201 156 202 method, encodedCreds = basicAuthHdr.split(None, 1) 157 if method.lower() != HttpBasicAuthMiddleware.HTTP_HDR_FIELDNAME: 203 if (method.lower() != 204 HttpBasicAuthMiddleware.AUTHN_SCHEME_HDR_FIELDNAME_LOWER): 158 205 log.debug("Auth method is %r not %r: skipping request", 159 method, HttpBasicAuthMiddleware.HTTP_HDR_FIELDNAME) 206 method, 207 HttpBasicAuthMiddleware.AUTHN_SCHEME_HDR_FIELDNAME) 160 208 return None, None 161 209 … … 166 214 def __call__(self, environ, start_response): 167 215 """Authenticate based HTTP header elements as specified by the HTTP 168 Basic Authentication spec.""" 216 Basic Authentication spec. 217 218 @param environ: WSGI environ 219 @type environ: dict-like type 220 @param start_response: WSGI start response function 221 @type start_response: function 222 @return: response 223 @rtype: iterable 224 """ 169 225 log.debug("HttpBasicAuthNMiddleware.__call__ ...") 170 226 171 227 if not self._pathMatch(environ): 172 228 return self.__app(environ, start_response) 173 229 230 def start_response_wrapper(status, headers): 231 """Ensure Authentication realm is included with 401 responses""" 232 statusCode = int(status.split()[0]) 233 if statusCode == httplib.UNAUTHORIZED: 234 authnRealmHdrFound = False 235 for name, val in headers: 236 if (name.lower() == 237 self.__class__.AUTHENTICATE_HDR_FIELDNAME_LOWER): 238 authnRealmHdrFound = True 239 break 240 241 if not authnRealmHdrFound: 242 authnRealmHdr = (self.__class__.AUTHENTICATE_HDR_FIELDNAME, 243 "%s %s" % ( 244 self.__class__.AUTHN_SCHEME_HDR_FIELDNAME, 245 self.realm)) 246 headers.append(authnRealmHdr) 247 248 return start_response(status, headers) 249 174 250 username, password = self._parseCredentials(environ) 175 251 if username is None: 176 252 log.error('No username set in HTTP Authorization header') 177 return self.setErrorResponse(start_response ,253 return self.setErrorResponse(start_response_wrapper, 178 254 msg="No username set\n") 179 255 … … 187 263 # the next middleware is called in the chain 188 264 try: 189 response = authenticateFunc(environ, start_response, username, 265 response = authenticateFunc(environ, 266 start_response_wrapper, 267 username, 190 268 password) 191 269 if response is None: 192 return self.__app(environ, start_response )270 return self.__app(environ, start_response_wrapper) 193 271 else: 194 272 return response 195 273 196 except HttpBasicAuth Unauthorized:197 log.error('Client authentication failed: %s',274 except HttpBasicAuthResponseException, e: 275 log.error('Client authentication raised an exception: %s', 198 276 traceback.format_exc()) 199 return self.setErrorResponse(start_response) 200 201 @staticmethod 202 def setErrorResponse(start_response, msg=None, 203 code=httplib.UNAUTHORIZED, contentType=None): 277 return self.setErrorResponse(start_response_wrapper, 278 msg=e.response, 279 code=e.code) 280 281 @classmethod 282 def setErrorResponse(cls, 283 start_response, 284 msg=None, 285 code=httplib.UNAUTHORIZED, 286 contentType=None): 204 287 '''Convenience method to set a simple error response 205 288 … … 222 305 if contentType is None: 223 306 contentType = 'text/plain' 224 225 start_response(status, 226 [('Content-type', contentType), 227 ('Content-Length', str(len(response)))]) 307 308 headers = [ 309 ('Content-type', contentType), 310 ('Content-length', str(len(msg))) 311 ] 312 start_response(status, headers) 228 313 return [response] -
TI12-security/trunk/MyProxyServerUtils/myproxy/server/wsgi/middleware.py
r6893 r6897 19 19 from OpenSSL import crypto 20 20 from myproxy.client import MyProxyClient, MyProxyClientError 21 21 from myproxy.server.wsgi.httpbasicauth import HttpBasicAuthResponseException 22 22 23 23 24 class MyProxyClientMiddlewareError(Exception): … … 185 186 def _myProxylogon(environ, start_response, username, password): 186 187 """Wrap MyProxy logon method as a WSGI app 187 """ 188 if environ.get('REQUEST_METHOD') == 'POST': 189 wsgiInput = environ[ 190 MyProxyClientMiddleware.WSGI_INPUT_ENV_KEYNAME] 191 192 contentLength = int(environ.get('CONTENT_LENGTH', -1)) 193 if contentLength == -1: 194 raise MyProxyClientMiddlewareError('No "CONTENT_LENGTH" ' 195 'setting found in ' 196 'environ') 197 198 pemCertReq = wsgiInput.read(contentLength) 199 200 # Restore WSGI file object with duck typing(!) 201 wsgiInput = StringIO() 202 wsgiInput.write(pemCertReq) 203 wsgiInput.seek(0) 204 205 # Expecting PEM encoded request 206 certReq = crypto.load_certificate_request( 207 crypto.FILETYPE_PEM, 208 pemCertReq) 209 210 # Convert to ASN1 format expect by logon client call 211 asn1CertReq = crypto.dump_certificate_request( 212 crypto.FILETYPE_ASN1, 213 certReq) 214 else: 215 status = self.getStatusMessage(httplib.UNAUTHORIZED) 216 response = ("HTTP request method %r not recognised for this " 217 "request " % environ.get('REQUEST_METHOD', 218 '<Not set>')) 219 log.error(response) 220 start_response(status, 221 [('Content-length', str(len(response))), 222 ('Content-type', 'text/plain')]) 223 return [response] 224 188 """ 189 requestMethod = environ.get('REQUEST_METHOD') 190 if requestMethod != 'POST': 191 response = "HTTP Request method not recognised" 192 log.error("HTTP Request method %r not recognised", 193 requestMethod) 194 raise HttpBasicAuthResponseException(response, 195 httplib.METHOD_NOT_ALLOWED) 196 197 wsgiInput = environ[MyProxyClientMiddleware.WSGI_INPUT_ENV_KEYNAME] 198 199 contentLength = int(environ.get('CONTENT_LENGTH', -1)) 200 if contentLength == -1: 201 raise MyProxyClientMiddlewareError('No "CONTENT_LENGTH" ' 202 'setting found in ' 203 'environ') 204 205 pemCertReq = wsgiInput.read(contentLength) 206 207 # Restore WSGI file object with duck typing(!) 208 wsgiInput = StringIO() 209 wsgiInput.write(pemCertReq) 210 wsgiInput.seek(0) 211 212 # Expecting PEM encoded request 213 try: 214 certReq = crypto.load_certificate_request(crypto.FILETYPE_PEM, 215 pemCertReq) 216 except crypto.Error, e: 217 log.error("Error loading input certificate request: %r", 218 pemCertReq) 219 raise HttpBasicAuthResponseException("Error loading input " 220 "certificate request", 221 httplib.BAD_REQUEST) 222 223 # Convert to ASN1 format expect by logon client call 224 asn1CertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, 225 certReq) 226 225 227 try: 226 228 credentials = self.myProxyClient.logon(username, … … 230 232 response = '\n'.join(credentials) 231 233 234 start_response(status, 235 [('Content-length', str(len(response))), 236 ('Content-type', 'text/plain')]) 237 return [response] 238 232 239 except MyProxyClientError, e: 233 status = self.getStatusMessage(httplib.UNAUTHORIZED) 234 response = str(e) 235 240 raise HttpBasicAuthResponseException(str(e), 241 httplib.UNAUTHORIZED) 236 242 except socket.error, e: 237 243 raise MyProxyClientMiddlewareError("Socket error " … … 243 249 self.myProxyClient.hostname, 244 250 traceback.format_exc()) 245 raise 246 247 start_response(status, 248 [('Content-length', str(len(response))), 249 ('Content-type', 'text/plain')]) 250 return [response] 251 raise # Trigger 500 Internal Server Error 252 253 251 254 252 255 return _myProxylogon
Note: See TracChangeset
for help on using the changeset viewer.