summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/changes/feature_4035_improve-error-messages3
-rw-r--r--common/src/leap/soledad/common/errors.py61
-rw-r--r--server/src/leap/soledad/server/auth.py62
3 files changed, 89 insertions, 37 deletions
diff --git a/common/changes/feature_4035_improve-error-messages b/common/changes/feature_4035_improve-error-messages
new file mode 100644
index 00000000..4f86a17f
--- /dev/null
+++ b/common/changes/feature_4035_improve-error-messages
@@ -0,0 +1,3 @@
+ o Improve error messages. Closes #5035.
+ * Add MissingTokenError and InvalidTokenError as sub exceptions from
+ Unauthorized.
diff --git a/common/src/leap/soledad/common/errors.py b/common/src/leap/soledad/common/errors.py
index f241ee06..446c4c75 100644
--- a/common/src/leap/soledad/common/errors.py
+++ b/common/src/leap/soledad/common/errors.py
@@ -25,16 +25,56 @@ from u1db import errors
from u1db.remote import http_errors
+def register_exception(cls):
+ """
+ A small decorator that registers exceptions in u1db maps.
+ """
+ # update u1db "wire description to status" and "wire description to
+ # exception" maps.
+ http_errors.wire_description_to_status.update({
+ cls.wire_description: cls.status})
+ errors.wire_description_to_exc.update({
+ cls.wire_description: cls})
+ # do not modify the exception
+ return cls
+
+
class SoledadError(errors.U1DBError):
"""
Base Soledad HTTP errors.
"""
pass
+
+#
+# Authorization errors
+#
+
+@register_exception
+class MissingAuthTokenError(errors.Unauthorized):
+ """
+ Exception raised when failing to get authorization for some action because
+ the auth token is missing in the tokens db.
+ """
+
+ wire_description = "missing token"
+ status = 401
+
+@register_exception
+class InvalidAuthTokenError(errors.Unauthorized):
+ """
+ Exception raised when failing to get authorization for some action because
+ the provided token is different from the one in the tokens db.
+ """
+
+ wire_descrition = "token mismatch"
+ status = 401
+
#
# LockResource errors
#
+@register_exception
class InvalidTokenError(SoledadError):
"""
Exception raised when trying to unlock shared database with invalid token.
@@ -44,6 +84,7 @@ class InvalidTokenError(SoledadError):
status = 401
+@register_exception
class NotLockedError(SoledadError):
"""
Exception raised when trying to unlock shared database when it is not
@@ -54,6 +95,7 @@ class NotLockedError(SoledadError):
status = 404
+@register_exception
class AlreadyLockedError(SoledadError):
"""
Exception raised when trying to lock shared database but it is already
@@ -64,6 +106,7 @@ class AlreadyLockedError(SoledadError):
status = 403
+@register_exception
class LockTimedOutError(SoledadError):
"""
Exception raised when timing out while trying to lock the shared database.
@@ -73,6 +116,7 @@ class LockTimedOutError(SoledadError):
status = 408
+@register_exception
class CouldNotObtainLockError(SoledadError):
"""
Exception raised when timing out while trying to lock the shared database.
@@ -86,6 +130,7 @@ class CouldNotObtainLockError(SoledadError):
# CouchDatabase errors
#
+@register_exception
class MissingDesignDocError(SoledadError):
"""
Raised when trying to access a missing couch design document.
@@ -95,6 +140,7 @@ class MissingDesignDocError(SoledadError):
status = 500
+@register_exception
class MissingDesignDocNamedViewError(SoledadError):
"""
Raised when trying to access a missing named view on a couch design
@@ -105,6 +151,7 @@ class MissingDesignDocNamedViewError(SoledadError):
status = 500
+@register_exception
class MissingDesignDocListFunctionError(SoledadError):
"""
Raised when trying to access a missing list function on a couch design
@@ -115,6 +162,7 @@ class MissingDesignDocListFunctionError(SoledadError):
status = 500
+@register_exception
class MissingDesignDocDeletedError(SoledadError):
"""
Raised when trying to access a deleted couch design document.
@@ -124,6 +172,7 @@ class MissingDesignDocDeletedError(SoledadError):
status = 500
+@register_exception
class DesignDocUnknownError(SoledadError):
"""
Raised when trying to access a couch design document and getting an
@@ -134,18 +183,6 @@ class DesignDocUnknownError(SoledadError):
status = 500
-# update u1db "wire description to status" and "wire description to exception"
-# maps.
-for e in [InvalidTokenError, NotLockedError, AlreadyLockedError,
- LockTimedOutError, CouldNotObtainLockError, MissingDesignDocError,
- MissingDesignDocListFunctionError, MissingDesignDocNamedViewError,
- MissingDesignDocDeletedError, DesignDocUnknownError]:
- http_errors.wire_description_to_status.update({
- e.wire_description: e.status})
- errors.wire_description_to_exc.update({
- e.wire_description: e})
-
-
# u1db error statuses also have to be updated
http_errors.ERROR_STATUSES = set(
http_errors.wire_description_to_status.values())
diff --git a/server/src/leap/soledad/server/auth.py b/server/src/leap/soledad/server/auth.py
index 0ae49576..11805005 100644
--- a/server/src/leap/soledad/server/auth.py
+++ b/server/src/leap/soledad/server/auth.py
@@ -36,19 +36,10 @@ from leap.soledad.common import (
SHARED_DB_NAME,
SHARED_DB_LOCK_DOC_ID_PREFIX,
USER_DB_PREFIX,
+ errors,
)
-#-----------------------------------------------------------------------------
-# Authentication
-#-----------------------------------------------------------------------------
-
-class Unauthorized(Exception):
- """
- User authentication failed.
- """
-
-
class URLToAuthorization(object):
"""
Verify if actions can be performed by a user.
@@ -279,10 +270,16 @@ class SoledadAuthMiddleware(object):
return self._unauthorized_error("Wrong authentication scheme")
# verify if user is athenticated
- if not self._verify_authentication_data(uuid, auth_data):
- return self._unauthorized_error(
+ try:
+ if not self._verify_authentication_data(uuid, auth_data):
+ return self._unauthorized_error(
+ start_response,
+ self._get_auth_error_string())
+ except Unauthorized as e:
+ return self._error(
start_response,
- self._get_auth_error_string())
+ 401,
+ e.wire_description)
# verify if user is authorized to perform action
if not self._verify_authorization(environ, uuid):
@@ -319,6 +316,9 @@ class SoledadAuthMiddleware(object):
@return: Whether the token is valid for authenticating the request.
@rtype: bool
+
+ @raise Unauthorized: Raised when C{auth_data} is not enough to
+ authenticate C{uuid}.
"""
return None
@@ -386,9 +386,20 @@ class SoledadTokenAuthMiddleware(SoledadAuthMiddleware):
@return: Whether the token is valid for authenticating the request.
@rtype: bool
+
+ @raise Unauthorized: Raised when C{auth_data} is not enough to
+ authenticate C{uuid}.
"""
token = auth_data # we expect a cleartext token at this point
- return self._verify_token_in_couchdb(uuid, token)
+ try:
+ return self._verify_token_in_couchdb(uuid, token)
+ except MissingAuthTokenError():
+ raise
+ except TokenMismatchError():
+ raise
+ except Exception as e:
+ log.err(e)
+ return False
def _verify_token_in_couchdb(self, uuid, token):
"""
@@ -398,19 +409,20 @@ class SoledadTokenAuthMiddleware(SoledadAuthMiddleware):
@type uuid: str
@param token: The token.
@type token: str
+
+ @raise MissingAuthTokenError: Raised when given token is missing in
+ tokens db.
+ @raise InvalidAuthTokenError: Raised when token is invalid.
"""
server = Server(url=self._app.state.couch_url)
- try:
- dbname = self.TOKENS_DB
- db = server[dbname]
- token = db.get(token)
- if token is None:
- return False
- return token[self.TOKENS_TYPE_KEY] == self.TOKENS_TYPE_DEF and \
- token[self.TOKENS_USER_ID_KEY] == uuid
- except Exception as e:
- log.err(e)
- return False
+ dbname = self.TOKENS_DB
+ db = server[dbname]
+ token = db.get(token)
+ if token is None:
+ raise MissingAuthTokenError()
+ if token[self.TOKENS_TYPE_KEY] != self.TOKENS_TYPE_DEF or \
+ token[self.TOKENS_USER_ID_KEY] != uuid:
+ raise InvalidAuthTokenError()
return True
def _get_auth_error_string(self):