diff options
| -rw-r--r-- | common/changes/feature_4035_improve-error-messages | 3 | ||||
| -rw-r--r-- | common/src/leap/soledad/common/errors.py | 61 | ||||
| -rw-r--r-- | server/src/leap/soledad/server/auth.py | 62 | 
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): | 
