]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
heat API : Return auth errors in AWS format
authorSteven Hardy <shardy@redhat.com>
Thu, 5 Jul 2012 20:53:36 +0000 (21:53 +0100)
committerSteven Hardy <shardy@redhat.com>
Fri, 6 Jul 2012 13:41:58 +0000 (14:41 +0100)
Update EC2 authentication code to return HeatAPIException
subclasses, so the API error response on auth failure is
aligned with AWS responses.
ref #125

Change-Id: Iafa33e7aed4d77f2255b4d879192b9d15a2395aa
Signed-off-by: Steven Hardy <shardy@redhat.com>
heat/api/v1/__init__.py
heat/api/v1/exception.py
heat/common/wsgi.py

index 8f0efbd4b9b47b4cdd7b653980969a7fbc476600..e5f1d1175d2e0d2fa30efd8422eae651a622ed08 100644 (file)
@@ -29,6 +29,7 @@ from webob import Request
 import webob
 from heat import utils
 from heat.common import context
+from heat.api.v1 import exception
 
 logger = logging.getLogger(__name__)
 
@@ -43,17 +44,28 @@ class EC2Token(wsgi.Middleware):
     @webob.dec.wsgify(RequestClass=wsgi.Request)
     def __call__(self, req):
         # Read request signature and access id.
+        # If we find KeyStoneCreds in the params we ignore a key error
+        # here so that we can use both authentication methods.
+        # Returning here just means the user didn't supply AWS
+        # authentication and we'll let the app try native keystone next.
         logger.info("Checking AWS credentials..")
         try:
             signature = req.params['Signature']
+        except KeyError:
+            logger.info("No AWS Signature found.")
+            if 'KeyStoneCreds' in req.params:
+                return self.application
+            else:
+                raise exception.HeatIncompleteSignatureError()
+
+        try:
             access = req.params['AWSAccessKeyId']
         except KeyError:
-            # We ignore a key error here so that we can use both
-            # authentication methods.  Returning here just means
-            # the user didn't supply AWS authentication and we'll let
-            # the app try native keystone next.
-            logger.info("No AWS credentials found.")
-            return self.application
+            logger.info("No AWSAccessKeyId found.")
+            if 'KeyStoneCreds' in req.params:
+                return self.application
+            else:
+                raise exception.HeatMissingAuthenticationTokenError()
 
         logger.info("AWS credentials found, checking against keystone.")
         # Make a copy of args for authentication and signature verification.
@@ -99,9 +111,20 @@ class EC2Token(wsgi.Middleware):
             token_id = result['access']['token']['id']
             logger.info("AWS authentication successful.")
         except (AttributeError, KeyError):
-            # FIXME: Should be 404 I think.
             logger.info("AWS authentication failure.")
-            raise webob.exc.HTTPBadRequest()
+            # Try to extract the reason for failure so we can return the
+            # appropriate AWS error via raising an exception
+            try:
+                reason = result['error']['message']
+            except KeyError:
+                reason = None
+
+            if reason == "EC2 access key not found.":
+                raise exception.HeatInvalidClientTokenIdError()
+            elif reason == "EC2 signature not supplied.":
+                raise exception.HeatSignatureError()
+            else:
+                raise exception.HeatAccessDeniedError()
 
         # Authenticated!
         req.headers['X-Auth-EC2-Creds'] = creds_json
index c643a7634977fa18cb2c64c21d696ad96d41b993..247a7f3ffeb7617f9cd5a1562acbfcb69de61e91 100644 (file)
@@ -18,6 +18,7 @@
 """Heat API exception subclasses - maps API response errors to AWS Errors"""
 
 import webob.exc
+from heat.common import wsgi
 
 
 class HeatAPIException(webob.exc.HTTPError):
@@ -32,6 +33,17 @@ class HeatAPIException(webob.exc.HTTPError):
     explanation = "Generic HeatAPIException, please use specific subclasses!"
     err_type = "Sender"
 
+    def __init__(self, detail=None):
+        '''
+        Overload HTTPError constructor, so we can create a default serialized
+        body.  This is required because not all error responses are processed
+        by the wsgi controller (ie auth errors, which are further up the
+        paste pipeline.  We serialize in XML by default (as AWS does)
+        '''
+        webob.exc.HTTPError.__init__(self, detail=detail)
+        serializer = wsgi.XMLResponseSerializer()
+        serializer.default(self, self.get_unserialized_body())
+
     def get_unserialized_body(self):
         '''
         Return a dict suitable for serialization in the wsgi controller
@@ -190,3 +202,25 @@ class HeatThrottlingError(HeatAPIException):
     code = 400
     title = "Throttling"
     explanation = "Request was denied due to request throttling"
+
+
+# Not documented in the AWS docs, authentication failure errors
+class HeatAccessDeniedError(HeatAPIException):
+    '''
+    This is the response given when authentication fails due to user
+    IAM group memberships meaning we deny access
+    '''
+    code = 403
+    title = "AccessDenied"
+    explanation = "User is not authorized to perform action"
+
+
+class HeatSignatureError(HeatAPIException):
+    '''
+    This is the response given when authentication fails due to
+    a bad signature
+    '''
+    code = 403
+    title = "SignatureDoesNotMatch"
+    explanation = ("The request signature we calculated does not match the " +
+                   "signature you provided")
index a29f29564f5438792fbbfdb27fb2677ec58e07cc..07924f93dbe0751176d9bbba871a3b1e8d4dfab6 100644 (file)
@@ -546,14 +546,18 @@ class Resource(object):
             # Here we should get API exceptions derived from HeatAPIException
             # these implement get_unserialized_body(), which allow us to get
             # a dict containing the unserialized error response.
+            # We only need to serialize for JSON content_type, as the
+            # exception body is pre-serialized to the default XML in the
+            # HeatAPIException constructor
             # If we get something else here (e.g a webob.exc exception),
             # this will fail, and we just return it without serializing,
             # which will not conform to the expected AWS error response format
-            try:
-                err_body = action_result.get_unserialized_body()
-                serializer.default(action_result, err_body)
-            except:
-                logging.warning("Unable to serialize exception response")
+            if content_type == "JSON":
+                try:
+                    err_body = action_result.get_unserialized_body()
+                    serializer.default(action_result, err_body)
+                except:
+                    logging.warning("Unable to serialize exception response")
 
             return action_result