Changeset 6573 for TI12-security/trunk/NDGSecurity
- Timestamp:
- 15/02/10 11:59:33 (11 years ago)
- Location:
- TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/saml
- Files:
-
- 2 added
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/saml/__init__.py
r6512 r6573 1 """WSGI SAML Module for SAML 2.0 Assertion Query/Request Profile implementation 1 """WSGI SAML package for SAML 2.0 Attribute and Authorisation Decision Query/ 2 Request Profile interfaces 2 3 3 4 NERC DataGrid Project 4 5 """ 5 6 __author__ = "P J Kershaw" 6 __date__ = "1 7/08/2009"7 __copyright__ = "(C) 20 09Science and Technology Facilities Council"7 __date__ = "15/02/10" 8 __copyright__ = "(C) 2010 Science and Technology Facilities Council" 8 9 __contact__ = "Philip.Kershaw@stfc.ac.uk" 9 10 __revision__ = "$Id: $" 10 11 __license__ = "BSD - see LICENSE file in top-levle directory" 11 import logging12 log = logging.getLogger(__name__)13 from cStringIO import StringIO14 from uuid import uuid415 from datetime import datetime16 from xml.etree import ElementTree17 18 from saml.saml2.core import (Response, Assertion, Attribute, AttributeValue,19 AttributeStatement, SAMLVersion, Subject, NameID,20 Issuer, AttributeQuery, XSStringAttributeValue,21 Conditions, Status, StatusCode)22 23 from saml.common.xml import SAMLConstants24 from saml.xml import UnknownAttrProfile25 from saml.xml.etree import (AssertionElementTree, AttributeQueryElementTree,26 ResponseElementTree, QName)27 28 from ndg.security.common.saml_utils.esg import XSGroupRoleAttributeValue29 from ndg.security.common.saml_utils.esg.xml.etree import (30 XSGroupRoleAttributeValueElementTree)31 from ndg.security.common.soap.etree import SOAPEnvelope32 from ndg.security.common.utils.etree import prettyPrint33 from ndg.security.server.wsgi import NDGSecurityPathFilter34 from ndg.security.server.wsgi.soap import SOAPMiddleware35 36 37 class SOAPAttributeInterfaceMiddlewareError(Exception):38 """Base class for WSGI SAML 2.0 SOAP Attribute Interface Errors"""39 40 41 class SOAPAttributeInterfaceMiddlewareConfigError(Exception):42 """WSGI SAML 2.0 SOAP Attribute Interface Configuration problem"""43 44 45 class SOAPAttributeInterfaceMiddleware(SOAPMiddleware, NDGSecurityPathFilter):46 """Implementation of SAML 2.0 SOAP Binding for Assertion Query/Request47 Profile48 49 @type PATH_OPTNAME: basestring50 @cvar PATH_OPTNAME: name of app_conf option for specifying a path or paths51 that this middleware will intercept and process52 @type QUERY_INTERFACE_KEYNAME_OPTNAME: basestring53 @cvar QUERY_INTERFACE_KEYNAME_OPTNAME: app_conf option name for key name54 used to reference the SAML query interface in environ55 @type DEFAULT_QUERY_INTERFACE_KEYNAME: basestring56 @param DEFAULT_QUERY_INTERFACE_KEYNAME: default key name for referencing57 SAML query interface in environ58 """59 log = logging.getLogger('SOAPAttributeInterfaceMiddleware')60 PATH_OPTNAME = "pathMatchList"61 QUERY_INTERFACE_KEYNAME_OPTNAME = "queryInterfaceKeyName"62 DEFAULT_QUERY_INTERFACE_KEYNAME = ("ndg.security.server.wsgi.saml."63 "SOAPAttributeInterfaceMiddleware.queryInterface")64 65 def __init__(self, app):66 '''@type app: callable following WSGI interface67 @param app: next middleware application in the chain68 '''69 NDGSecurityPathFilter.__init__(self, app, None)70 71 self._app = app72 73 def initialise(self, global_conf, prefix='', **app_conf):74 '''75 @type global_conf: dict76 @param global_conf: PasteDeploy global configuration dictionary77 @type prefix: basestring78 @param prefix: prefix for configuration items79 @type app_conf: dict80 @param app_conf: PasteDeploy application specific configuration81 dictionary82 '''83 self.__queryInterfaceKeyName = None84 85 self.pathMatchList = app_conf.get(86 prefix + SOAPAttributeInterfaceMiddleware.PATH_OPTNAME, ['/'])87 88 self.queryInterfaceKeyName = app_conf.get(prefix + \89 SOAPAttributeInterfaceMiddleware.QUERY_INTERFACE_KEYNAME_OPTNAME,90 prefix + \91 SOAPAttributeInterfaceMiddleware.DEFAULT_QUERY_INTERFACE_KEYNAME)92 93 @classmethod94 def filter_app_factory(cls, app, global_conf, **app_conf):95 """Set-up using a Paste app factory pattern. Set this method to avoid96 possible conflicts from multiple inheritance97 98 @type app: callable following WSGI interface99 @param app: next middleware application in the chain100 @type global_conf: dict101 @param global_conf: PasteDeploy global configuration dictionary102 @type prefix: basestring103 @param prefix: prefix for configuration items104 @type app_conf: dict105 @param app_conf: PasteDeploy application specific configuration106 dictionary107 """108 app = cls(app)109 app.initialise(global_conf, **app_conf)110 111 return app112 113 def _getQueryInterfaceKeyName(self):114 return self.__queryInterfaceKeyName115 116 def _setQueryInterfaceKeyName(self, value):117 if not isinstance(value, basestring):118 raise TypeError('Expecting string type for "queryInterfaceKeyName"'119 ' got %r' % value)120 121 self.__queryInterfaceKeyName = value122 123 queryInterfaceKeyName = property(fget=_getQueryInterfaceKeyName,124 fset=_setQueryInterfaceKeyName,125 doc="environ keyname for Attribute Query "126 "interface")127 128 def _getIssuerName(self):129 return self.__issuerName130 131 def _setIssuerName(self, value):132 self.__issuerName = value133 134 issuerName = property(fget=_getIssuerName,135 fset=_setIssuerName,136 doc="Name of assertion issuing authority")137 138 @NDGSecurityPathFilter.initCall139 def __call__(self, environ, start_response):140 """Check for and parse a SOAP SAML Attribute Query and return a141 SAML Response142 143 @type environ: dict144 @param environ: WSGI environment variables dictionary145 @type start_response: function146 @param start_response: standard WSGI start response function147 """148 149 # Ignore non-matching path150 if not self.pathMatch:151 return self._app(environ, start_response)152 153 # Ignore non-POST requests154 if environ.get('REQUEST_METHOD') != 'POST':155 return self._app(environ, start_response)156 157 soapRequestStream = environ.get('wsgi.input')158 if soapRequestStream is None:159 raise SOAPAttributeInterfaceMiddlewareError('No "wsgi.input" in '160 'environ')161 162 # TODO: allow for chunked data163 contentLength = environ.get('CONTENT_LENGTH')164 if contentLength is None:165 raise SOAPAttributeInterfaceMiddlewareError('No "CONTENT_LENGTH" '166 'in environ')167 168 contentLength = int(contentLength)169 soapRequestTxt = soapRequestStream.read(contentLength)170 171 # Parse into a SOAP envelope object172 soapRequest = SOAPEnvelope()173 soapRequest.parse(StringIO(soapRequestTxt))174 175 # Filter based on SOAP Body content - expecting an AttributeQuery176 # element177 if not SOAPAttributeInterfaceMiddleware.isAttributeQuery(178 soapRequest.body):179 # Reset wsgi.input for middleware and app downstream180 environ['wsgi.input'] = StringIO(soapRequestTxt)181 return self._app(environ, start_response)182 183 log.debug("SOAPAttributeInterfaceMiddleware.__call__: received SAML "184 "SOAP AttributeQuery ...")185 186 attributeQueryElem = soapRequest.body.elem[0]187 188 try:189 attributeQuery = AttributeQueryElementTree.fromXML(190 attributeQueryElem)191 except UnknownAttrProfile, e:192 log.exception("Parsing incoming attribute query: " % e)193 samlResponse = self._makeErrorResponse(194 StatusCode.UNKNOWN_ATTR_PROFILE_URI)195 else:196 # Check for Query Interface in environ197 queryInterface = environ.get(self.queryInterfaceKeyName)198 if queryInterface is None:199 raise SOAPAttributeInterfaceMiddlewareConfigError(200 'No query interface "%s" key found in environ'%201 self.queryInterfaceKeyName)202 203 # Call query interface204 samlResponse = queryInterface(attributeQuery)205 206 # Add mapping for ESG Group/Role Attribute Value to enable ElementTree207 # Attribute Value factory to render the XML output208 toXMLTypeMap = {209 XSGroupRoleAttributeValue: XSGroupRoleAttributeValueElementTree210 }211 212 # Convert to ElementTree representation to enable attachment to SOAP213 # response body214 samlResponseElem = ResponseElementTree.toXML(samlResponse,215 customToXMLTypeMap=toXMLTypeMap)216 xml = ElementTree.tostring(samlResponseElem)217 218 # Create SOAP response and attach the SAML Response payload219 soapResponse = SOAPEnvelope()220 soapResponse.create()221 soapResponse.body.elem.append(samlResponseElem)222 223 response = soapResponse.serialize()224 225 log.debug("SOAPAttributeInterfaceMiddleware.__call__: sending response "226 "...\n\n%s",227 response)228 start_response("200 OK",229 [('Content-length', str(len(response))),230 ('Content-type', 'text/xml')])231 return [response]232 233 @classmethod234 def isAttributeQuery(cls, soapBody):235 """Check for AttributeQuery in the SOAP Body"""236 237 if len(soapBody.elem) != 1:238 # TODO: Change to a SOAP Fault?239 raise SOAPAttributeInterfaceMiddlewareError("Expecting single "240 "child element in the "241 "request SOAP "242 "Envelope body")243 244 inputQName = QName(soapBody.elem[0].tag)245 attributeQueryQName = QName.fromGeneric(246 AttributeQuery.DEFAULT_ELEMENT_NAME)247 return inputQName == attributeQueryQName248 249 def _makeErrorResponse(self, code):250 """Convenience method for making a basic response following an error251 """252 samlResponse = Response()253 254 samlResponse.issueInstant = datetime.utcnow()255 samlResponse.id = str(uuid4())256 257 # Initialise to success status but reset on error258 samlResponse.status = Status()259 samlResponse.status.statusCode = StatusCode()260 samlResponse.status.statusCode.value = code261 262 return samlResponse
Note: See TracChangeset
for help on using the changeset viewer.