Rename ValidationError to HttpError.

master
Tom Hacohen 4 years ago
parent 34c548acda
commit a75d5479fa

@ -25,7 +25,7 @@ from django_etebase.token_auth.models import AuthToken
from django_etebase.token_auth.models import get_default_expiry from django_etebase.token_auth.models import get_default_expiry
from django_etebase.utils import create_user, get_user_queryset, CallbackContext from django_etebase.utils import create_user, get_user_queryset, CallbackContext
from django_etebase.views import msgpack_encode, msgpack_decode from django_etebase.views import msgpack_encode, msgpack_decode
from .exceptions import AuthenticationFailed, transform_validation_error, ValidationError from .exceptions import AuthenticationFailed, transform_validation_error, HttpError
from .msgpack import MsgpackRoute from .msgpack import MsgpackRoute
from .utils import BaseModel from .utils import BaseModel
@ -207,20 +207,20 @@ def validate_login_request(
challenge_data = msgpack_decode(box.decrypt(validated_data.challenge)) challenge_data = msgpack_decode(box.decrypt(validated_data.challenge))
now = int(datetime.now().timestamp()) now = int(datetime.now().timestamp())
if validated_data.action != expected_action: if validated_data.action != expected_action:
raise ValidationError("wrong_action", f'Expected "{challenge_sent_to_user.response}" but got something else') raise HttpError("wrong_action", f'Expected "{challenge_sent_to_user.response}" but got something else')
elif now - challenge_data["timestamp"] > app_settings.CHALLENGE_VALID_SECONDS: elif now - challenge_data["timestamp"] > app_settings.CHALLENGE_VALID_SECONDS:
raise ValidationError("challenge_expired", "Login challenge has expired") raise HttpError("challenge_expired", "Login challenge has expired")
elif challenge_data["userId"] != user.id: elif challenge_data["userId"] != user.id:
raise ValidationError("wrong_user", "This challenge is for the wrong user") raise HttpError("wrong_user", "This challenge is for the wrong user")
elif not settings.DEBUG and validated_data.host.split(":", 1)[0] != host_from_request: elif not settings.DEBUG and validated_data.host.split(":", 1)[0] != host_from_request:
raise ValidationError( raise HttpError(
"wrong_host", f'Found wrong host name. Got: "{validated_data.host}" expected: "{host_from_request}"' "wrong_host", f'Found wrong host name. Got: "{validated_data.host}" expected: "{host_from_request}"'
) )
verify_key = nacl.signing.VerifyKey(bytes(user.userinfo.loginPubkey), encoder=nacl.encoding.RawEncoder) verify_key = nacl.signing.VerifyKey(bytes(user.userinfo.loginPubkey), encoder=nacl.encoding.RawEncoder)
try: try:
verify_key.verify(challenge_sent_to_user.response, challenge_sent_to_user.signature) verify_key.verify(challenge_sent_to_user.response, challenge_sent_to_user.signature)
except nacl.exceptions.BadSignatureError: except nacl.exceptions.BadSignatureError:
raise ValidationError("login_bad_signature", "Wrong password for user.", status.HTTP_401_UNAUTHORIZED) raise HttpError("login_bad_signature", "Wrong password for user.", status.HTTP_401_UNAUTHORIZED)
@authentication_router.get("/is_etebase/") @authentication_router.get("/is_etebase/")
@ -269,7 +269,7 @@ def dashboard_url(user: User = Depends(get_authenticated_user)):
# XXX-TOM # XXX-TOM
get_dashboard_url = app_settings.DASHBOARD_URL_FUNC get_dashboard_url = app_settings.DASHBOARD_URL_FUNC
if get_dashboard_url is None: if get_dashboard_url is None:
raise ValidationError("not_supported", "This server doesn't have a user dashboard.") raise HttpError("not_supported", "This server doesn't have a user dashboard.")
ret = { ret = {
"url": get_dashboard_url(request, *args, **kwargs), "url": get_dashboard_url(request, *args, **kwargs),
@ -301,7 +301,7 @@ def signup_save(data: SignupIn, request: Request) -> User:
raise EtebaseValidationError("generic", str(e)) raise EtebaseValidationError("generic", str(e))
if hasattr(instance, "userinfo"): if hasattr(instance, "userinfo"):
raise ValidationError("user_exists", "User already exists", status_code=status.HTTP_409_CONFLICT) raise HttpError("user_exists", "User already exists", status_code=status.HTTP_409_CONFLICT)
models.UserInfo.objects.create(**data.dict(exclude={"user"}), owner=instance) models.UserInfo.objects.create(**data.dict(exclude={"user"}), owner=instance)
return instance return instance

@ -11,7 +11,7 @@ from fastapi import APIRouter, Depends, status
from django_etebase import models from django_etebase import models
from .authentication import get_authenticated_user from .authentication import get_authenticated_user
from .exceptions import ValidationError, transform_validation_error, PermissionDenied from .exceptions import HttpError, transform_validation_error, PermissionDenied
from .msgpack import MsgpackRoute from .msgpack import MsgpackRoute
from .stoken_handler import filter_by_stoken_and_limit, filter_by_stoken, get_stoken_obj, get_queryset_stoken from .stoken_handler import filter_by_stoken_and_limit, filter_by_stoken, get_stoken_obj, get_queryset_stoken
from .utils import get_object_or_404, Context, Prefetch, PrefetchQuery, is_collection_admin, BaseModel from .utils import get_object_or_404, Context, Prefetch, PrefetchQuery, is_collection_admin, BaseModel
@ -144,7 +144,7 @@ class ItemDepIn(BaseModel):
item = models.CollectionItem.objects.get(uid=self.uid) item = models.CollectionItem.objects.get(uid=self.uid)
etag = self.etag etag = self.etag
if item.etag != etag: if item.etag != etag:
raise ValidationError( raise HttpError(
"wrong_etag", "wrong_etag",
"Wrong etag. Expected {} got {}".format(item.etag, etag), "Wrong etag. Expected {} got {}".format(item.etag, etag),
status_code=status.HTTP_409_CONFLICT, status_code=status.HTTP_409_CONFLICT,
@ -274,7 +274,7 @@ def process_revisions_for_item(item: models.CollectionItem, revision_data: Colle
chunk_obj.chunkFile.save("IGNORED", ContentFile(content)) chunk_obj.chunkFile.save("IGNORED", ContentFile(content))
chunk_obj.save() chunk_obj.save()
else: else:
raise ValidationError("chunk_no_content", "Tried to create a new chunk without content") raise HttpError("chunk_no_content", "Tried to create a new chunk without content")
chunks_objs.append(chunk_obj) chunks_objs.append(chunk_obj)
@ -290,12 +290,12 @@ def process_revisions_for_item(item: models.CollectionItem, revision_data: Colle
def _create(data: CollectionIn, user: User): def _create(data: CollectionIn, user: User):
with transaction.atomic(): with transaction.atomic():
if data.item.etag is not None: if data.item.etag is not None:
raise ValidationError("bad_etag", "etag is not null") raise HttpError("bad_etag", "etag is not null")
instance = models.Collection(uid=data.item.uid, owner=user) instance = models.Collection(uid=data.item.uid, owner=user)
try: try:
instance.validate_unique() instance.validate_unique()
except django_exceptions.ValidationError: except django_exceptions.ValidationError:
raise ValidationError( raise HttpError(
"unique_uid", "Collection with this uid already exists", status_code=status.HTTP_409_CONFLICT "unique_uid", "Collection with this uid already exists", status_code=status.HTTP_409_CONFLICT
) )
instance.save() instance.save()
@ -355,7 +355,7 @@ def item_create(item_model: CollectionItemIn, collection: models.Collection, val
return instance return instance
if validate_etag and cur_etag != etag: if validate_etag and cur_etag != etag:
raise ValidationError( raise HttpError(
"wrong_etag", "wrong_etag",
"Wrong etag. Expected {} got {}".format(cur_etag, etag), "Wrong etag. Expected {} got {}".format(cur_etag, etag),
status_code=status.HTTP_409_CONFLICT, status_code=status.HTTP_409_CONFLICT,
@ -425,7 +425,7 @@ def item_bulk_common(data: ItemBatchIn, user: User, stoken: t.Optional[str], uid
collection_object = queryset.select_for_update().get(uid=uid) collection_object = queryset.select_for_update().get(uid=uid)
if stoken is not None and stoken != collection_object.stoken: if stoken is not None and stoken != collection_object.stoken:
raise ValidationError("stale_stoken", "Stoken is too old", status_code=status.HTTP_409_CONFLICT) raise HttpError("stale_stoken", "Stoken is too old", status_code=status.HTTP_409_CONFLICT)
# XXX-TOM: make sure we return compatible errors # XXX-TOM: make sure we return compatible errors
data.validate_db() data.validate_db()
@ -482,7 +482,7 @@ def fetch_updates(
item_limit = 200 item_limit = 200
if len(data) > item_limit: if len(data) > item_limit:
raise ValidationError("too_many_items", "Request has too many items.", status_code=status.HTTP_400_BAD_REQUEST) raise HttpError("too_many_items", "Request has too many items.", status_code=status.HTTP_400_BAD_REQUEST)
queryset, stoken_rev = filter_by_stoken(stoken, queryset, models.CollectionItem.stoken_annotation) queryset, stoken_rev = filter_by_stoken(stoken, queryset, models.CollectionItem.stoken_annotation)

@ -3,19 +3,17 @@ import typing as t
from pydantic import BaseModel from pydantic import BaseModel
from django_etebase.exceptions import EtebaseValidationError
class HttpErrorField(BaseModel):
class ValidationErrorField(BaseModel):
field: str field: str
code: str code: str
detail: str detail: str
class ValidationErrorOut(BaseModel): class HttpErrorOut(BaseModel):
code: str code: str
detail: str detail: str
errors: t.Optional[t.List[ValidationErrorField]] errors: t.Optional[t.List[HttpErrorField]]
class CustomHttpException(Exception): class CustomHttpException(Exception):
@ -59,24 +57,23 @@ class PermissionDenied(CustomHttpException):
super().__init__(code=code, detail=detail, status_code=status_code) super().__init__(code=code, detail=detail, status_code=status_code)
class ValidationError(CustomHttpException): class HttpError(CustomHttpException):
def __init__( def __init__(
self, self,
code: str, code: str,
detail: str, detail: str,
status_code: int = status.HTTP_400_BAD_REQUEST, status_code: int = status.HTTP_400_BAD_REQUEST,
field: t.Optional[str] = None, errors: t.Optional[t.List["HttpError"]] = None,
errors: t.Optional[t.List["ValidationError"]] = None,
): ):
self.errors = errors self.errors = errors
super().__init__(code=code, detail=detail, status_code=status_code) super().__init__(code=code, detail=detail, status_code=status_code)
@property @property
def as_dict(self) -> dict: def as_dict(self) -> dict:
return ValidationErrorOut(code=self.code, errors=self.errors, detail=self.detail).dict() return HttpErrorOut(code=self.code, errors=self.errors, detail=self.detail).dict()
def flatten_errors(field_name, errors) -> t.List[ValidationError]: def flatten_errors(field_name, errors) -> t.List[HttpError]:
ret = [] ret = []
if isinstance(errors, dict): if isinstance(errors, dict):
for error_key in errors: for error_key in errors:
@ -98,5 +95,5 @@ def transform_validation_error(prefix, err):
elif not hasattr(err, "message"): elif not hasattr(err, "message"):
errors = flatten_errors(prefix, err.error_list) errors = flatten_errors(prefix, err.error_list)
else: else:
raise EtebaseValidationError(err.code, err.message) raise HttpError(err.code, err.message)
raise ValidationError(code="field_errors", detail="Field validations failed.", errors=errors) raise HttpError(code="field_errors", detail="Field validations failed.", errors=errors)

@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, status, Request
from django_etebase import models from django_etebase import models
from django_etebase.utils import get_user_queryset, CallbackContext from django_etebase.utils import get_user_queryset, CallbackContext
from .authentication import get_authenticated_user from .authentication import get_authenticated_user
from .exceptions import ValidationError, PermissionDenied from .exceptions import HttpError, PermissionDenied
from .msgpack import MsgpackRoute from .msgpack import MsgpackRoute
from .utils import get_object_or_404, Context, is_collection_admin, BaseModel from .utils import get_object_or_404, Context, is_collection_admin, BaseModel
@ -42,7 +42,7 @@ class CollectionInvitationCommon(BaseModel):
class CollectionInvitationIn(CollectionInvitationCommon): class CollectionInvitationIn(CollectionInvitationCommon):
def validate_db(self, context: Context): def validate_db(self, context: Context):
if context.user.username == self.username.lower(): if context.user.username == self.username.lower():
raise ValidationError("no_self_invite", "Inviting yourself is not allowed") raise HttpError("no_self_invite", "Inviting yourself is not allowed")
class CollectionInvitationOut(CollectionInvitationCommon): class CollectionInvitationOut(CollectionInvitationCommon):
@ -186,7 +186,7 @@ def outgoing_create(
**data.dict(exclude={"collection", "username"}), user=to_user, fromMember=member **data.dict(exclude={"collection", "username"}), user=to_user, fromMember=member
) )
except IntegrityError: except IntegrityError:
raise ValidationError("invitation_exists", "Invitation already exists") raise HttpError("invitation_exists", "Invitation already exists")
@invitation_outgoing_router.get("/", response_model=InvitationListResponse) @invitation_outgoing_router.get("/", response_model=InvitationListResponse)

@ -10,7 +10,7 @@ from django.contrib.auth import get_user_model
from django_etebase.models import AccessLevels from django_etebase.models import AccessLevels
from .exceptions import ValidationError from .exceptions import HttpError
User = get_user_model() User = get_user_model()
@ -35,7 +35,7 @@ def get_object_or_404(queryset: QuerySet, **kwargs):
try: try:
return queryset.get(**kwargs) return queryset.get(**kwargs)
except ObjectDoesNotExist as e: except ObjectDoesNotExist as e:
raise ValidationError("does_not_exist", str(e), status_code=status.HTTP_404_NOT_FOUND) raise HttpError("does_not_exist", str(e), status_code=status.HTTP_404_NOT_FOUND)
def is_collection_admin(collection, user): def is_collection_admin(collection, user):

Loading…
Cancel
Save