Repository: auth0/auth0-python

Issue:
Auth0Error not being raised due to inconsistent API error responses
Currently `Auth0Error` is raised whenever the API response contains an `error` key in the response JSON. Unfortunately at least one endpoint (`/dbconnections/signup`) returns inconsistent error messages (that do not always contain the `error` key) for different scenarios and as a result `Auth0Error` is not raised when an error occurs.

Examples of inconsistent responses:
* when making a signup request with an email that is already registered: 
```
{
    "code": "user_exists", 
    "description": "The user already exists.", 
    "name": "BadRequestError", 
    "statusCode": 400
}
```

* when making a request with an invalid `client_id` (with public signup disabled)
```
{
    "name": "NotFoundError", 
    "statusCode": 404
}
```

* when making a request with an invalid password (with password strength enabled)
```
{
    "code": "invalid_password", 
    "description": {
        "rules": [
            {
                "code": "lengthAtLeast", 
                "format": [
                    6
                ], 
                "message": "At least %d characters in length", 
                "verified": false
            }
        ], 
        "verified": false
    }, 
    "message": "Password is too weak", 
    "name": "PasswordStrengthError", 
    "policy": "* At least 6 characters in length", 
    "statusCode": 400
}
```

* when making a request with missing password
```
{
    "error": "password is required"
}
```

The last example highlights a related issue. Even though there is an `error` key, a `KeyError` exception will ultimately occur because `AuthenticationBase._process_response` assumes the additional existence of an `error_description` key when creating the `Auth0Error` and setting its message.

Discussion:

Patch:
diff --git a/auth0/v3/authentication/base.py b/auth0/v3/authentication/base.py
index b4ccd1d..778912c 100644
--- a/auth0/v3/authentication/base.py
+++ b/auth0/v3/authentication/base.py
@@ -19,8 +19,8 @@ class AuthenticationBase(object):
         except ValueError:
             return response.text
         else:
-            if 'error' in text:
-                raise Auth0Error(status_code=text['error'],
-                                 error_code=text['error'],
-                                 message=text['error_description'])
+            if response.status_code is None or response.status_code >= 400:
+                raise Auth0Error(status_code=response.status_code,
+                                 error_code=text.get('error', ''),
+                                 message=text.get('error_description', ''))
         return text


Test patch:
diff --git a/auth0/v3/test/authentication/test_base.py b/auth0/v3/test/authentication/test_base.py
index c058864..d6539c8 100644
--- a/auth0/v3/test/authentication/test_base.py
+++ b/auth0/v3/test/authentication/test_base.py
@@ -10,6 +10,7 @@ class TestBase(unittest.TestCase):
     def test_post(self, mock_post):
         ab = AuthenticationBase()
 
+        mock_post.return_value.status_code = 200
         mock_post.return_value.text = '{"x": "y"}'
 
         data = ab.post('the-url', data={'a': 'b'}, headers={'c': 'd'})
@@ -23,12 +24,14 @@ class TestBase(unittest.TestCase):
     def test_post_error(self, mock_post):
         ab = AuthenticationBase()
 
-        mock_post.return_value.text = '{"error": "e0",' \
-                                      '"error_description": "desc"}'
+        for error_status in [400, 500, None]:
+            mock_post.return_value.status_code = error_status
+            mock_post.return_value.text = '{"error": "e0",' \
+                                          '"error_description": "desc"}'
 
-        with self.assertRaises(Auth0Error) as context:
-            data = ab.post('the-url', data={'a': 'b'}, headers={'c': 'd'})
+            with self.assertRaises(Auth0Error) as context:
+                data = ab.post('the-url', data={'a': 'b'}, headers={'c': 'd'})
 
-        self.assertEqual(context.exception.status_code, 'e0')
-        self.assertEqual(context.exception.error_code, 'e0')
-        self.assertEqual(context.exception.message, 'desc')
+            self.assertEqual(context.exception.status_code, error_status)
+            self.assertEqual(context.exception.error_code, 'e0')
+            self.assertEqual(context.exception.message, 'desc')