@ -45,34 +45,34 @@ from .drf_msgpack.renderers import MessagePackRenderer
from . import app_settings , permissions
from . import app_settings , permissions
from . renderers import JSONRenderer
from . renderers import JSONRenderer
from . models import (
from . models import (
Collection ,
Collection ,
CollectionItem ,
CollectionItem ,
CollectionItemRevision ,
CollectionItemRevision ,
CollectionMember ,
CollectionMember ,
CollectionMemberRemoved ,
CollectionMemberRemoved ,
CollectionInvitation ,
CollectionInvitation ,
Stoken ,
Stoken ,
UserInfo ,
UserInfo ,
)
)
from . serializers import (
from . serializers import (
AuthenticationChangePasswordInnerSerializer ,
AuthenticationChangePasswordInnerSerializer ,
AuthenticationSignupSerializer ,
AuthenticationSignupSerializer ,
AuthenticationLoginChallengeSerializer ,
AuthenticationLoginChallengeSerializer ,
AuthenticationLoginSerializer ,
AuthenticationLoginSerializer ,
AuthenticationLoginInnerSerializer ,
AuthenticationLoginInnerSerializer ,
CollectionSerializer ,
CollectionSerializer ,
CollectionItemSerializer ,
CollectionItemSerializer ,
CollectionItemBulkGetSerializer ,
CollectionItemBulkGetSerializer ,
CollectionItemDepSerializer ,
CollectionItemDepSerializer ,
CollectionItemRevisionSerializer ,
CollectionItemRevisionSerializer ,
CollectionItemChunkSerializer ,
CollectionItemChunkSerializer ,
CollectionListMultiSerializer ,
CollectionListMultiSerializer ,
CollectionMemberSerializer ,
CollectionMemberSerializer ,
CollectionInvitationSerializer ,
CollectionInvitationSerializer ,
InvitationAcceptSerializer ,
InvitationAcceptSerializer ,
UserInfoPubkeySerializer ,
UserInfoPubkeySerializer ,
UserSerializer ,
UserSerializer ,
)
)
from . utils import get_user_queryset
from . utils import get_user_queryset
from . exceptions import EtebaseValidationError
from . exceptions import EtebaseValidationError
from . parsers import ChunkUploadParser
from . parsers import ChunkUploadParser
@ -99,8 +99,8 @@ class BaseViewSet(viewsets.ModelViewSet):
def get_serializer_class ( self ) :
def get_serializer_class ( self ) :
serializer_class = self . serializer_class
serializer_class = self . serializer_class
if self . request . method == ' PUT ' :
if self . request . method == " PUT " :
serializer_class = getattr ( self , ' serializer_update_class ' , serializer_class )
serializer_class = getattr ( self , " serializer_update_class " , serializer_class )
return serializer_class
return serializer_class
@ -109,7 +109,7 @@ class BaseViewSet(viewsets.ModelViewSet):
return queryset . filter ( members__user = user )
return queryset . filter ( members__user = user )
def get_stoken_obj_id ( self , request ) :
def get_stoken_obj_id ( self , request ) :
return request . GET . get ( ' stoken ' , None )
return request . GET . get ( " stoken " , None )
def get_stoken_obj ( self , request ) :
def get_stoken_obj ( self , request ) :
stoken = self . get_stoken_obj_id ( request )
stoken = self . get_stoken_obj_id ( request )
@ -118,7 +118,7 @@ class BaseViewSet(viewsets.ModelViewSet):
try :
try :
return Stoken . objects . get ( uid = stoken )
return Stoken . objects . get ( uid = stoken )
except Stoken . DoesNotExist :
except Stoken . DoesNotExist :
raise EtebaseValidationError ( ' bad_stoken ' , ' Invalid stoken. ' , status_code = status . HTTP_400_BAD_REQUEST )
raise EtebaseValidationError ( " bad_stoken " , " Invalid stoken. " , status_code = status . HTTP_400_BAD_REQUEST )
return None
return None
@ -127,7 +127,7 @@ class BaseViewSet(viewsets.ModelViewSet):
aggr_fields = [ Coalesce ( Max ( field ) , V ( 0 ) ) for field in self . stoken_id_fields ]
aggr_fields = [ Coalesce ( Max ( field ) , V ( 0 ) ) for field in self . stoken_id_fields ]
max_stoken = Greatest ( * aggr_fields ) if len ( aggr_fields ) > 1 else aggr_fields [ 0 ]
max_stoken = Greatest ( * aggr_fields ) if len ( aggr_fields ) > 1 else aggr_fields [ 0 ]
queryset = queryset . annotate ( max_stoken = max_stoken ) . order_by ( ' max_stoken ' )
queryset = queryset . annotate ( max_stoken = max_stoken ) . order_by ( " max_stoken " )
if stoken_rev is not None :
if stoken_rev is not None :
queryset = queryset . filter ( max_stoken__gt = stoken_rev . id )
queryset = queryset . filter ( max_stoken__gt = stoken_rev . id )
@ -137,18 +137,18 @@ class BaseViewSet(viewsets.ModelViewSet):
def get_queryset_stoken ( self , queryset ) :
def get_queryset_stoken ( self , queryset ) :
maxid = - 1
maxid = - 1
for row in queryset :
for row in queryset :
rowmaxid = getattr ( row , ' max_stoken ' ) or - 1
rowmaxid = getattr ( row , " max_stoken " ) or - 1
maxid = max ( maxid , rowmaxid )
maxid = max ( maxid , rowmaxid )
new_stoken = ( maxid > = 0 ) and Stoken . objects . get ( id = maxid )
new_stoken = ( maxid > = 0 ) and Stoken . objects . get ( id = maxid )
return new_stoken or None
return new_stoken or None
def filter_by_stoken_and_limit ( self , request , queryset ) :
def filter_by_stoken_and_limit ( self , request , queryset ) :
limit = int ( request . GET . get ( ' limit ' , 50 ) )
limit = int ( request . GET . get ( " limit " , 50 ) )
queryset , stoken_rev = self . filter_by_stoken ( request , queryset )
queryset , stoken_rev = self . filter_by_stoken ( request , queryset )
result = list ( queryset [ : limit + 1 ] )
result = list ( queryset [ : limit + 1 ] )
if len ( result ) < limit + 1 :
if len ( result ) < limit + 1 :
done = True
done = True
else :
else :
@ -165,21 +165,21 @@ class BaseViewSet(viewsets.ModelViewSet):
serializer = self . get_serializer ( queryset , many = True )
serializer = self . get_serializer ( queryset , many = True )
ret = {
ret = {
' data ' : serializer . data ,
" data " : serializer . data ,
' done ' : True , # we always return all the items, so it's always done
" done " : True , # we always return all the items, so it's always done
}
}
return Response ( ret )
return Response ( ret )
class CollectionViewSet ( BaseViewSet ) :
class CollectionViewSet ( BaseViewSet ) :
allowed_methods = [ ' GET ' , ' POST ' ]
allowed_methods = [ " GET " , " POST " ]
permission_classes = BaseViewSet . permission_classes + ( permissions . IsCollectionAdminOrReadOnly , )
permission_classes = BaseViewSet . permission_classes + ( permissions . IsCollectionAdminOrReadOnly , )
queryset = Collection . objects . all ( )
queryset = Collection . objects . all ( )
serializer_class = CollectionSerializer
serializer_class = CollectionSerializer
lookup_field = ' main_item__uid '
lookup_field = " main_item__uid "
lookup_url_kwarg = ' uid '
lookup_url_kwarg = " uid "
stoken_id_fields = [ ' items__revisions__stoken__id ' , ' members__stoken__id ' ]
stoken_id_fields = [ " items__revisions__stoken__id " , " members__stoken__id " ]
def get_queryset ( self , queryset = None ) :
def get_queryset ( self , queryset = None ) :
if queryset is None :
if queryset is None :
@ -188,8 +188,8 @@ class CollectionViewSet(BaseViewSet):
def get_serializer_context ( self ) :
def get_serializer_context ( self ) :
context = super ( ) . get_serializer_context ( )
context = super ( ) . get_serializer_context ( )
prefetch = self . request . query_params . get ( ' prefetch ' , ' auto ' )
prefetch = self . request . query_params . get ( " prefetch " , " auto " )
context . update ( { ' request ' : self . request , ' prefetch ' : prefetch } )
context . update ( { " request " : self . request , " prefetch " : prefetch } )
return context
return context
def destroy ( self , request , uid = None , * args , * * kwargs ) :
def destroy ( self , request , uid = None , * args , * * kwargs ) :
@ -213,17 +213,18 @@ class CollectionViewSet(BaseViewSet):
queryset = self . get_queryset ( )
queryset = self . get_queryset ( )
return self . list_common ( request , queryset , * args , * * kwargs )
return self . list_common ( request , queryset , * args , * * kwargs )
@action_decorator ( detail = False , methods = [ ' POST ' ] )
@action_decorator ( detail = False , methods = [ " POST " ] )
def list_multi ( self , request , * args , * * kwargs ) :
def list_multi ( self , request , * args , * * kwargs ) :
serializer = CollectionListMultiSerializer ( data = request . data )
serializer = CollectionListMultiSerializer ( data = request . data )
serializer . is_valid ( raise_exception = True )
serializer . is_valid ( raise_exception = True )
collection_types = serializer . validated_data [ ' collectionTypes ' ]
collection_types = serializer . validated_data [ " collectionTypes " ]
queryset = self . get_queryset ( )
queryset = self . get_queryset ( )
# FIXME: Remove the isnull part once we attach collection types to all objects ("collection-type-migration")
# FIXME: Remove the isnull part once we attach collection types to all objects ("collection-type-migration")
queryset = queryset . filter (
queryset = queryset . filter (
Q ( members__collectionType__uid__in = collection_types ) | Q ( members__collectionType__isnull = True ) )
Q ( members__collectionType__uid__in = collection_types ) | Q ( members__collectionType__isnull = True )
)
return self . list_common ( request , queryset , * args , * * kwargs )
return self . list_common ( request , queryset , * args , * * kwargs )
@ -234,51 +235,50 @@ class CollectionViewSet(BaseViewSet):
serializer = self . get_serializer ( result , many = True )
serializer = self . get_serializer ( result , many = True )
ret = {
ret = {
' data ' : serializer . data ,
" data " : serializer . data ,
' stoken ' : new_stoken ,
" stoken " : new_stoken ,
' done ' : done ,
" done " : done ,
}
}
stoken_obj = self . get_stoken_obj ( request )
stoken_obj = self . get_stoken_obj ( request )
if stoken_obj is not None :
if stoken_obj is not None :
# FIXME: honour limit? (the limit should be combined for data and this because of stoken)
# FIXME: honour limit? (the limit should be combined for data and this because of stoken)
remed_qs = CollectionMemberRemoved . objects . filter ( user = request . user , stoken__id__gt = stoken_obj . id )
remed_qs = CollectionMemberRemoved . objects . filter ( user = request . user , stoken__id__gt = stoken_obj . id )
if not ret [ ' done ' ] :
if not ret [ " done " ] :
# We only filter by the new_stoken if we are not done. This is because if we are done, the new stoken
# We only filter by the new_stoken if we are not done. This is because if we are done, the new stoken
# can point to the most recent collection change rather than most recent removed membership.
# can point to the most recent collection change rather than most recent removed membership.
remed_qs = remed_qs . filter ( stoken__id__lte = new_stoken_obj . id )
remed_qs = remed_qs . filter ( stoken__id__lte = new_stoken_obj . id )
remed = remed_qs . values_list ( ' collection__main_item__uid ' , flat = True )
remed = remed_qs . values_list ( " collection__main_item__uid " , flat = True )
if len ( remed ) > 0 :
if len ( remed ) > 0 :
ret [ ' removedMemberships ' ] = [ { ' uid ' : x } for x in remed ]
ret [ " removedMemberships " ] = [ { " uid " : x } for x in remed ]
return Response ( ret )
return Response ( ret )
class CollectionItemViewSet ( BaseViewSet ) :
class CollectionItemViewSet ( BaseViewSet ) :
allowed_methods = [ ' GET ' , ' POST ' , ' PUT ' ]
allowed_methods = [ " GET " , " POST " , " PUT " ]
permission_classes = BaseViewSet . permission_classes + ( permissions . HasWriteAccessOrReadOnly , )
permission_classes = BaseViewSet . permission_classes + ( permissions . HasWriteAccessOrReadOnly , )
queryset = CollectionItem . objects . all ( )
queryset = CollectionItem . objects . all ( )
serializer_class = CollectionItemSerializer
serializer_class = CollectionItemSerializer
lookup_field = ' uid '
lookup_field = " uid "
stoken_id_fields = [ ' revisions__stoken__id ' ]
stoken_id_fields = [ " revisions__stoken__id " ]
def get_queryset ( self ) :
def get_queryset ( self ) :
collection_uid = self . kwargs [ ' collection_uid ' ]
collection_uid = self . kwargs [ " collection_uid " ]
try :
try :
collection = self . get_collection_queryset ( Collection . objects ) . get ( main_item__uid = collection_uid )
collection = self . get_collection_queryset ( Collection . objects ) . get ( main_item__uid = collection_uid )
except Collection . DoesNotExist :
except Collection . DoesNotExist :
raise Http404 ( " Collection does not exist " )
raise Http404 ( " Collection does not exist " )
# XXX Potentially add this for performance: .prefetch_related('revisions__chunks')
# XXX Potentially add this for performance: .prefetch_related('revisions__chunks')
queryset = type ( self ) . queryset . filter ( collection__pk = collection . pk ,
queryset = type ( self ) . queryset . filter ( collection__pk = collection . pk , revisions__current = True )
revisions__current = True )
return queryset
return queryset
def get_serializer_context ( self ) :
def get_serializer_context ( self ) :
context = super ( ) . get_serializer_context ( )
context = super ( ) . get_serializer_context ( )
prefetch = self . request . query_params . get ( ' prefetch ' , ' auto ' )
prefetch = self . request . query_params . get ( " prefetch " , " auto " )
context . update ( { ' request ' : self . request , ' prefetch ' : prefetch } )
context . update ( { " request " : self . request , " prefetch " : prefetch } )
return context
return context
def create ( self , request , collection_uid = None , * args , * * kwargs ) :
def create ( self , request , collection_uid = None , * args , * * kwargs ) :
@ -298,7 +298,7 @@ class CollectionItemViewSet(BaseViewSet):
def list ( self , request , collection_uid = None , * args , * * kwargs ) :
def list ( self , request , collection_uid = None , * args , * * kwargs ) :
queryset = self . get_queryset ( )
queryset = self . get_queryset ( )
if not self . request . query_params . get ( ' withCollection ' , False ) :
if not self . request . query_params . get ( " withCollection " , False ) :
queryset = queryset . filter ( parent__isnull = True )
queryset = queryset . filter ( parent__isnull = True )
result , new_stoken_obj , done = self . filter_by_stoken_and_limit ( request , queryset )
result , new_stoken_obj , done = self . filter_by_stoken_and_limit ( request , queryset )
@ -307,27 +307,27 @@ class CollectionItemViewSet(BaseViewSet):
serializer = self . get_serializer ( result , many = True )
serializer = self . get_serializer ( result , many = True )
ret = {
ret = {
' data ' : serializer . data ,
" data " : serializer . data ,
' stoken ' : new_stoken ,
" stoken " : new_stoken ,
' done ' : done ,
" done " : done ,
}
}
return Response ( ret )
return Response ( ret )
@action_decorator ( detail = True , methods = [ ' GET ' ] )
@action_decorator ( detail = True , methods = [ " GET " ] )
def revision ( self , request , collection_uid = None , uid = None , * args , * * kwargs ) :
def revision ( self , request , collection_uid = None , uid = None , * args , * * kwargs ) :
col = get_object_or_404 ( self . get_collection_queryset ( Collection . objects ) , main_item__uid = collection_uid )
col = get_object_or_404 ( self . get_collection_queryset ( Collection . objects ) , main_item__uid = collection_uid )
item = get_object_or_404 ( col . items , uid = uid )
item = get_object_or_404 ( col . items , uid = uid )
limit = int ( request . GET . get ( ' limit ' , 50 ) )
limit = int ( request . GET . get ( " limit " , 50 ) )
iterator = request . GET . get ( ' iterator ' , None )
iterator = request . GET . get ( " iterator " , None )
queryset = item . revisions . order_by ( ' -id ' )
queryset = item . revisions . order_by ( " -id " )
if iterator is not None :
if iterator is not None :
iterator = get_object_or_404 ( queryset , uid = iterator )
iterator = get_object_or_404 ( queryset , uid = iterator )
queryset = queryset . filter ( id__lt = iterator . id )
queryset = queryset . filter ( id__lt = iterator . id )
result = list ( queryset [ : limit + 1 ] )
result = list ( queryset [ : limit + 1 ] )
if len ( result ) < limit + 1 :
if len ( result ) < limit + 1 :
done = True
done = True
else :
else :
@ -336,17 +336,17 @@ class CollectionItemViewSet(BaseViewSet):
serializer = CollectionItemRevisionSerializer ( result , context = self . get_serializer_context ( ) , many = True )
serializer = CollectionItemRevisionSerializer ( result , context = self . get_serializer_context ( ) , many = True )
iterator = serializer . data [ - 1 ] [ ' uid ' ] if len ( result ) > 0 else None
iterator = serializer . data [ - 1 ] [ " uid " ] if len ( result ) > 0 else None
ret = {
ret = {
' data ' : serializer . data ,
" data " : serializer . data ,
' iterator ' : iterator ,
" iterator " : iterator ,
' done ' : done ,
" done " : done ,
}
}
return Response ( ret )
return Response ( ret )
# FIXME: rename to something consistent with what the clients have - maybe list_updates?
# FIXME: rename to something consistent with what the clients have - maybe list_updates?
@action_decorator ( detail = False , methods = [ ' POST ' ] )
@action_decorator ( detail = False , methods = [ " POST " ] )
def fetch_updates ( self , request , collection_uid = None , * args , * * kwargs ) :
def fetch_updates ( self , request , collection_uid = None , * args , * * kwargs ) :
queryset = self . get_queryset ( )
queryset = self . get_queryset ( )
@ -356,79 +356,76 @@ class CollectionItemViewSet(BaseViewSet):
item_limit = 200
item_limit = 200
if len ( serializer . validated_data ) > item_limit :
if len ( serializer . validated_data ) > item_limit :
content = { ' code ' : ' too_many_items ' ,
content = { " code " : " too_many_items " , " detail " : " Request has too many items. Limit: {} " . format ( item_limit ) }
' detail ' : ' Request has too many items. Limit: {} ' . format ( item_limit ) }
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
queryset , stoken_rev = self . filter_by_stoken ( request , queryset )
queryset , stoken_rev = self . filter_by_stoken ( request , queryset )
uids , etags = zip ( * [ ( item [ ' uid ' ] , item . get ( ' etag ' ) ) for item in serializer . validated_data ] )
uids , etags = zip ( * [ ( item [ " uid " ] , item . get ( " etag " ) ) for item in serializer . validated_data ] )
revs = CollectionItemRevision . objects . filter ( uid__in = etags , current = True )
revs = CollectionItemRevision . objects . filter ( uid__in = etags , current = True )
queryset = queryset . filter ( uid__in = uids ) . exclude ( revisions__in = revs )
queryset = queryset . filter ( uid__in = uids ) . exclude ( revisions__in = revs )
new_stoken_obj = self . get_queryset_stoken ( queryset )
new_stoken_obj = self . get_queryset_stoken ( queryset )
new_stoken = new_stoken_obj and new_stoken_obj . uid
new_stoken = new_stoken_obj and new_stoken_obj . uid
stoken = stoken_rev and getattr ( stoken_rev , ' uid ' , None )
stoken = stoken_rev and getattr ( stoken_rev , " uid " , None )
new_stoken = new_stoken or stoken
new_stoken = new_stoken or stoken
serializer = self . get_serializer ( queryset , many = True )
serializer = self . get_serializer ( queryset , many = True )
ret = {
ret = {
' data ' : serializer . data ,
" data " : serializer . data ,
' stoken ' : new_stoken ,
" stoken " : new_stoken ,
' done ' : True , # we always return all the items, so it's always done
" done " : True , # we always return all the items, so it's always done
}
}
return Response ( ret )
return Response ( ret )
@action_decorator ( detail = False , methods = [ ' POST ' ] )
@action_decorator ( detail = False , methods = [ " POST " ] )
def batch ( self , request , collection_uid = None , * args , * * kwargs ) :
def batch ( self , request , collection_uid = None , * args , * * kwargs ) :
return self . transaction ( request , collection_uid , validate_etag = False )
return self . transaction ( request , collection_uid , validate_etag = False )
@action_decorator ( detail = False , methods = [ ' POST ' ] )
@action_decorator ( detail = False , methods = [ " POST " ] )
def transaction ( self , request , collection_uid = None , validate_etag = True , * args , * * kwargs ) :
def transaction ( self , request , collection_uid = None , validate_etag = True , * args , * * kwargs ) :
stoken = request . GET . get ( ' stoken ' , None )
stoken = request . GET . get ( " stoken " , None )
with transaction . atomic ( ) : # We need this for locking on the collection object
with transaction . atomic ( ) : # We need this for locking on the collection object
collection_object = get_object_or_404 (
collection_object = get_object_or_404 (
self . get_collection_queryset ( Collection . objects ) . select_for_update ( ) , # Lock writes on the collection
self . get_collection_queryset ( Collection . objects ) . select_for_update ( ) , # Lock writes on the collection
main_item__uid = collection_uid )
main_item__uid = collection_uid ,
)
if stoken is not None and stoken != collection_object . stoken :
if stoken is not None and stoken != collection_object . stoken :
content = { ' code ' : ' stale_stoken ' , ' detail ' : ' Stoken is too old ' }
content = { " code " : " stale_stoken " , " detail " : " Stoken is too old " }
return Response ( content , status = status . HTTP_409_CONFLICT )
return Response ( content , status = status . HTTP_409_CONFLICT )
items = request . data . get ( ' items ' )
items = request . data . get ( " items " )
deps = request . data . get ( ' deps ' , None )
deps = request . data . get ( " deps " , None )
# FIXME: It should just be one serializer
# FIXME: It should just be one serializer
context = self . get_serializer_context ( )
context = self . get_serializer_context ( )
context . update ( { ' validate_etag ' : validate_etag } )
context . update ( { " validate_etag " : validate_etag } )
serializer = self . get_serializer_class ( ) ( data = items , context = context , many = True )
serializer = self . get_serializer_class ( ) ( data = items , context = context , many = True )
deps_serializer = CollectionItemDepSerializer ( data = deps , context = context , many = True )
deps_serializer = CollectionItemDepSerializer ( data = deps , context = context , many = True )
ser_valid = serializer . is_valid ( )
ser_valid = serializer . is_valid ( )
deps_ser_valid = ( deps is None or deps_serializer . is_valid ( ) )
deps_ser_valid = deps is None or deps_serializer . is_valid ( )
if ser_valid and deps_ser_valid :
if ser_valid and deps_ser_valid :
items = serializer . save ( collection = collection_object )
items = serializer . save ( collection = collection_object )
ret = {
ret = { }
}
return Response ( ret , status = status . HTTP_200_OK )
return Response ( ret , status = status . HTTP_200_OK )
return Response (
return Response (
{
{ " items " : serializer . errors , " deps " : deps_serializer . errors if deps is not None else [ ] , } ,
" items " : serializer . errors ,
status = status . HTTP_409_CONFLICT ,
" deps " : deps_serializer . errors if deps is not None else [ ] ,
)
} ,
status = status . HTTP_409_CONFLICT )
class CollectionItemChunkViewSet ( viewsets . ViewSet ) :
class CollectionItemChunkViewSet ( viewsets . ViewSet ) :
allowed_methods = [ ' GET ' , ' PUT ' ]
allowed_methods = [ " GET " , " PUT " ]
authentication_classes = BaseViewSet . authentication_classes
authentication_classes = BaseViewSet . authentication_classes
permission_classes = BaseViewSet . permission_classes
permission_classes = BaseViewSet . permission_classes
renderer_classes = BaseViewSet . renderer_classes
renderer_classes = BaseViewSet . renderer_classes
parser_classes = ( ChunkUploadParser , )
parser_classes = ( ChunkUploadParser , )
serializer_class = CollectionItemChunkSerializer
serializer_class = CollectionItemChunkSerializer
lookup_field = ' uid '
lookup_field = " uid "
def get_serializer_class ( self ) :
def get_serializer_class ( self ) :
return self . serializer_class
return self . serializer_class
@ -452,13 +449,12 @@ class CollectionItemChunkViewSet(viewsets.ViewSet):
serializer . save ( collection = col )
serializer . save ( collection = col )
except IntegrityError :
except IntegrityError :
return Response (
return Response (
{ " code " : " chunk_exists " , " detail " : " Chunk already exists. " } ,
{ " code " : " chunk_exists " , " detail " : " Chunk already exists. " } , status = status . HTTP_409_CONFLICT
status = status . HTTP_409_CONFLICT
)
)
return Response ( { } , status = status . HTTP_201_CREATED )
return Response ( { } , status = status . HTTP_201_CREATED )
@action_decorator ( detail = True , methods = [ ' GET ' ] )
@action_decorator ( detail = True , methods = [ " GET " ] )
def download ( self , request , collection_uid = None , collection_item_uid = None , uid = None , * args , * * kwargs ) :
def download ( self , request , collection_uid = None , collection_item_uid = None , uid = None , * args , * * kwargs ) :
import os
import os
from django . views . static import serve
from django . views . static import serve
@ -476,24 +472,24 @@ class CollectionItemChunkViewSet(viewsets.ViewSet):
class CollectionMemberViewSet ( BaseViewSet ) :
class CollectionMemberViewSet ( BaseViewSet ) :
allowed_methods = [ ' GET ' , ' PUT ' , ' DELETE ' ]
allowed_methods = [ " GET " , " PUT " , " DELETE " ]
our_base_permission_classes = BaseViewSet . permission_classes
our_base_permission_classes = BaseViewSet . permission_classes
permission_classes = our_base_permission_classes + ( permissions . IsCollectionAdmin , )
permission_classes = our_base_permission_classes + ( permissions . IsCollectionAdmin , )
queryset = CollectionMember . objects . all ( )
queryset = CollectionMember . objects . all ( )
serializer_class = CollectionMemberSerializer
serializer_class = CollectionMemberSerializer
lookup_field = f ' user__ { User . USERNAME_FIELD } __iexact '
lookup_field = f " user__ { User . USERNAME_FIELD } __iexact "
lookup_url_kwarg = ' username '
lookup_url_kwarg = " username "
stoken_id_fields = [ ' stoken__id ' ]
stoken_id_fields = [ " stoken__id " ]
# FIXME: need to make sure that there's always an admin, and maybe also don't let an owner remove adm access
# FIXME: need to make sure that there's always an admin, and maybe also don't let an owner remove adm access
# (if we want to transfer, we need to do that specifically)
# (if we want to transfer, we need to do that specifically)
def get_queryset ( self , queryset = None ) :
def get_queryset ( self , queryset = None ) :
collection_uid = self . kwargs [ ' collection_uid ' ]
collection_uid = self . kwargs [ " collection_uid " ]
try :
try :
collection = self . get_collection_queryset ( Collection . objects ) . get ( main_item__uid = collection_uid )
collection = self . get_collection_queryset ( Collection . objects ) . get ( main_item__uid = collection_uid )
except Collection . DoesNotExist :
except Collection . DoesNotExist :
raise Http404 ( ' Collection does not exist ' )
raise Http404 ( " Collection does not exist " )
if queryset is None :
if queryset is None :
queryset = type ( self ) . queryset
queryset = type ( self ) . queryset
@ -502,18 +498,18 @@ class CollectionMemberViewSet(BaseViewSet):
# We override this method because we expect the stoken to be called iterator
# We override this method because we expect the stoken to be called iterator
def get_stoken_obj_id ( self , request ) :
def get_stoken_obj_id ( self , request ) :
return request . GET . get ( ' iterator ' , None )
return request . GET . get ( " iterator " , None )
def list ( self , request , collection_uid = None , * args , * * kwargs ) :
def list ( self , request , collection_uid = None , * args , * * kwargs ) :
queryset = self . get_queryset ( ) . order_by ( ' id ' )
queryset = self . get_queryset ( ) . order_by ( " id " )
result , new_stoken_obj , done = self . filter_by_stoken_and_limit ( request , queryset )
result , new_stoken_obj , done = self . filter_by_stoken_and_limit ( request , queryset )
new_stoken = new_stoken_obj and new_stoken_obj . uid
new_stoken = new_stoken_obj and new_stoken_obj . uid
serializer = self . get_serializer ( result , many = True )
serializer = self . get_serializer ( result , many = True )
ret = {
ret = {
' data ' : serializer . data ,
" data " : serializer . data ,
' iterator ' : new_stoken , # Here we call it an iterator, it's only stoken for collection/items
" iterator " : new_stoken , # Here we call it an iterator, it's only stoken for collection/items
' done ' : done ,
" done " : done ,
}
}
return Response ( ret )
return Response ( ret )
@ -526,9 +522,9 @@ class CollectionMemberViewSet(BaseViewSet):
def perform_destroy ( self , instance ) :
def perform_destroy ( self , instance ) :
instance . revoke ( )
instance . revoke ( )
@action_decorator ( detail = False , methods = [ ' POST ' ] , permission_classes = our_base_permission_classes )
@action_decorator ( detail = False , methods = [ " POST " ] , permission_classes = our_base_permission_classes )
def leave ( self , request , collection_uid = None , * args , * * kwargs ) :
def leave ( self , request , collection_uid = None , * args , * * kwargs ) :
collection_uid = self . kwargs [ ' collection_uid ' ]
collection_uid = self . kwargs [ " collection_uid " ]
col = get_object_or_404 ( self . get_collection_queryset ( Collection . objects ) , main_item__uid = collection_uid )
col = get_object_or_404 ( self . get_collection_queryset ( Collection . objects ) , main_item__uid = collection_uid )
member = col . members . get ( user = request . user )
member = col . members . get ( user = request . user )
@ -540,20 +536,20 @@ class CollectionMemberViewSet(BaseViewSet):
class InvitationBaseViewSet ( BaseViewSet ) :
class InvitationBaseViewSet ( BaseViewSet ) :
queryset = CollectionInvitation . objects . all ( )
queryset = CollectionInvitation . objects . all ( )
serializer_class = CollectionInvitationSerializer
serializer_class = CollectionInvitationSerializer
lookup_field = ' uid '
lookup_field = " uid "
lookup_url_kwarg = ' invitation_uid '
lookup_url_kwarg = " invitation_uid "
def list ( self , request , collection_uid = None , * args , * * kwargs ) :
def list ( self , request , collection_uid = None , * args , * * kwargs ) :
limit = int ( request . GET . get ( ' limit ' , 50 ) )
limit = int ( request . GET . get ( " limit " , 50 ) )
iterator = request . GET . get ( ' iterator ' , None )
iterator = request . GET . get ( " iterator " , None )
queryset = self . get_queryset ( ) . order_by ( ' id ' )
queryset = self . get_queryset ( ) . order_by ( " id " )
if iterator is not None :
if iterator is not None :
iterator = get_object_or_404 ( queryset , uid = iterator )
iterator = get_object_or_404 ( queryset , uid = iterator )
queryset = queryset . filter ( id__gt = iterator . id )
queryset = queryset . filter ( id__gt = iterator . id )
result = list ( queryset [ : limit + 1 ] )
result = list ( queryset [ : limit + 1 ] )
if len ( result ) < limit + 1 :
if len ( result ) < limit + 1 :
done = True
done = True
else :
else :
@ -562,19 +558,19 @@ class InvitationBaseViewSet(BaseViewSet):
serializer = self . get_serializer ( result , many = True )
serializer = self . get_serializer ( result , many = True )
iterator = serializer . data [ - 1 ] [ ' uid ' ] if len ( result ) > 0 else None
iterator = serializer . data [ - 1 ] [ " uid " ] if len ( result ) > 0 else None
ret = {
ret = {
' data ' : serializer . data ,
" data " : serializer . data ,
' iterator ' : iterator ,
" iterator " : iterator ,
' done ' : done ,
" done " : done ,
}
}
return Response ( ret )
return Response ( ret )
class InvitationOutgoingViewSet ( InvitationBaseViewSet ) :
class InvitationOutgoingViewSet ( InvitationBaseViewSet ) :
allowed_methods = [ ' GET ' , ' POST ' , ' PUT ' , ' DELETE ' ]
allowed_methods = [ " GET " , " POST " , " PUT " , " DELETE " ]
def get_queryset ( self , queryset = None ) :
def get_queryset ( self , queryset = None ) :
if queryset is None :
if queryset is None :
@ -585,28 +581,29 @@ class InvitationOutgoingViewSet(InvitationBaseViewSet):
def create ( self , request , * args , * * kwargs ) :
def create ( self , request , * args , * * kwargs ) :
serializer = self . get_serializer ( data = request . data )
serializer = self . get_serializer ( data = request . data )
serializer . is_valid ( raise_exception = True )
serializer . is_valid ( raise_exception = True )
collection_uid = serializer . validated_data . get ( ' collection ' , { } ) . get ( ' uid ' )
collection_uid = serializer . validated_data . get ( " collection " , { } ) . get ( " uid " )
try :
try :
collection = self . get_collection_queryset ( Collection . objects ) . get ( main_item__uid = collection_uid )
collection = self . get_collection_queryset ( Collection . objects ) . get ( main_item__uid = collection_uid )
except Collection . DoesNotExist :
except Collection . DoesNotExist :
raise Http404 ( ' Collection does not exist ' )
raise Http404 ( " Collection does not exist " )
if request . user == serializer . validated_data . get ( ' user ' ) :
if request . user == serializer . validated_data . get ( " user " ) :
content = { ' code ' : ' self_invite ' , ' detail ' : ' Inviting yourself is invalid ' }
content = { " code " : " self_invite " , " detail " : " Inviting yourself is invalid " }
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
if not permissions . is_collection_admin ( collection , request . user ) :
if not permissions . is_collection_admin ( collection , request . user ) :
raise PermissionDenied ( { ' code ' : ' admin_access_required ' ,
raise PermissionDenied (
' detail ' : ' User is not an admin of this collection ' } )
{ " code " : " admin_access_required " , " detail " : " User is not an admin of this collection " }
)
serializer . save ( collection = collection )
serializer . save ( collection = collection )
return Response ( { } , status = status . HTTP_201_CREATED )
return Response ( { } , status = status . HTTP_201_CREATED )
@action_decorator ( detail = False , allowed_methods = [ ' GET ' ] , methods = [ ' GET ' ] )
@action_decorator ( detail = False , allowed_methods = [ " GET " ] , methods = [ " GET " ] )
def fetch_user_profile ( self , request , * args , * * kwargs ) :
def fetch_user_profile ( self , request , * args , * * kwargs ) :
username = request . GET . get ( ' username ' )
username = request . GET . get ( " username " )
kwargs = { User . USERNAME_FIELD : username . lower ( ) }
kwargs = { User . USERNAME_FIELD : username . lower ( ) }
user = get_object_or_404 ( get_user_queryset ( User . objects . all ( ) , self ) , * * kwargs )
user = get_object_or_404 ( get_user_queryset ( User . objects . all ( ) , self ) , * * kwargs )
user_info = get_object_or_404 ( UserInfo . objects . all ( ) , owner = user )
user_info = get_object_or_404 ( UserInfo . objects . all ( ) , owner = user )
@ -615,7 +612,7 @@ class InvitationOutgoingViewSet(InvitationBaseViewSet):
class InvitationIncomingViewSet ( InvitationBaseViewSet ) :
class InvitationIncomingViewSet ( InvitationBaseViewSet ) :
allowed_methods = [ ' GET ' , ' DELETE ' ]
allowed_methods = [ " GET " , " DELETE " ]
def get_queryset ( self , queryset = None ) :
def get_queryset ( self , queryset = None ) :
if queryset is None :
if queryset is None :
@ -623,11 +620,11 @@ class InvitationIncomingViewSet(InvitationBaseViewSet):
return queryset . filter ( user = self . request . user )
return queryset . filter ( user = self . request . user )
@action_decorator ( detail = True , allowed_methods = [ ' POST ' ] , methods = [ ' POST ' ] )
@action_decorator ( detail = True , allowed_methods = [ " POST " ] , methods = [ " POST " ] )
def accept ( self , request , invitation_uid = None , * args , * * kwargs ) :
def accept ( self , request , invitation_uid = None , * args , * * kwargs ) :
invitation = get_object_or_404 ( self . get_queryset ( ) , uid = invitation_uid )
invitation = get_object_or_404 ( self . get_queryset ( ) , uid = invitation_uid )
context = self . get_serializer_context ( )
context = self . get_serializer_context ( )
context . update ( { ' invitation ' : invitation } )
context . update ( { " invitation " : invitation } )
serializer = InvitationAcceptSerializer ( data = request . data , context = context )
serializer = InvitationAcceptSerializer ( data = request . data , context = context )
serializer . is_valid ( raise_exception = True )
serializer . is_valid ( raise_exception = True )
@ -636,36 +633,37 @@ class InvitationIncomingViewSet(InvitationBaseViewSet):
class AuthenticationViewSet ( viewsets . ViewSet ) :
class AuthenticationViewSet ( viewsets . ViewSet ) :
allowed_methods = [ ' POST ' ]
allowed_methods = [ " POST " ]
authentication_classes = BaseViewSet . authentication_classes
authentication_classes = BaseViewSet . authentication_classes
renderer_classes = BaseViewSet . renderer_classes
renderer_classes = BaseViewSet . renderer_classes
parser_classes = BaseViewSet . parser_classes
parser_classes = BaseViewSet . parser_classes
def get_encryption_key ( self , salt ) :
def get_encryption_key ( self , salt ) :
key = nacl . hash . blake2b ( settings . SECRET_KEY . encode ( ) , encoder = nacl . encoding . RawEncoder )
key = nacl . hash . blake2b ( settings . SECRET_KEY . encode ( ) , encoder = nacl . encoding . RawEncoder )
return nacl . hash . blake2b ( b ' ' , key = key , salt = salt [ : nacl . hash . BLAKE2B_SALTBYTES ] , person = b ' etebase-auth ' ,
return nacl . hash . blake2b (
encoder = nacl . encoding . RawEncoder )
b " " ,
key = key ,
salt = salt [ : nacl . hash . BLAKE2B_SALTBYTES ] ,
person = b " etebase-auth " ,
encoder = nacl . encoding . RawEncoder ,
)
def get_queryset ( self ) :
def get_queryset ( self ) :
return get_user_queryset ( User . objects . all ( ) , self )
return get_user_queryset ( User . objects . all ( ) , self )
def get_serializer_context ( self ) :
def get_serializer_context ( self ) :
return {
return { " request " : self . request , " format " : self . format_kwarg , " view " : self }
' request ' : self . request ,
' format ' : self . format_kwarg ,
' view ' : self
}
def login_response_data ( self , user ) :
def login_response_data ( self , user ) :
return {
return {
' token ' : AuthToken . objects . create ( user = user ) . key ,
" token " : AuthToken . objects . create ( user = user ) . key ,
' user ' : UserSerializer ( user ) . data ,
" user " : UserSerializer ( user ) . data ,
}
}
def list ( self , request , * args , * * kwargs ) :
def list ( self , request , * args , * * kwargs ) :
return Response ( status = status . HTTP_405_METHOD_NOT_ALLOWED )
return Response ( status = status . HTTP_405_METHOD_NOT_ALLOWED )
@action_decorator ( detail = False , methods = [ ' POST ' ] )
@action_decorator ( detail = False , methods = [ " POST " ] )
def signup ( self , request , * args , * * kwargs ) :
def signup ( self , request , * args , * * kwargs ) :
serializer = AuthenticationSignupSerializer ( data = request . data , context = self . get_serializer_context ( ) )
serializer = AuthenticationSignupSerializer ( data = request . data , context = self . get_serializer_context ( ) )
serializer . is_valid ( raise_exception = True )
serializer . is_valid ( raise_exception = True )
@ -677,23 +675,23 @@ class AuthenticationViewSet(viewsets.ViewSet):
return Response ( data , status = status . HTTP_201_CREATED )
return Response ( data , status = status . HTTP_201_CREATED )
def get_login_user ( self , username ) :
def get_login_user ( self , username ) :
kwargs = { User . USERNAME_FIELD + ' __iexact ' : username . lower ( ) }
kwargs = { User . USERNAME_FIELD + " __iexact " : username . lower ( ) }
try :
try :
user = self . get_queryset ( ) . get ( * * kwargs )
user = self . get_queryset ( ) . get ( * * kwargs )
if not hasattr ( user , ' userinfo ' ) :
if not hasattr ( user , " userinfo " ) :
raise AuthenticationFailed ( { ' code ' : ' user_not_init ' , ' detail ' : ' User not properly init ' } )
raise AuthenticationFailed ( { " code " : " user_not_init " , " detail " : " User not properly init " } )
return user
return user
except User . DoesNotExist :
except User . DoesNotExist :
raise AuthenticationFailed ( { ' code ' : ' user_not_found ' , ' detail ' : ' User not found ' } )
raise AuthenticationFailed ( { " code " : " user_not_found " , " detail " : " User not found " } )
def validate_login_request ( self , request , validated_data , response_raw , signature , expected_action ) :
def validate_login_request ( self , request , validated_data , response_raw , signature , expected_action ) :
from datetime import datetime
from datetime import datetime
username = validated_data . get ( ' username ' )
username = validated_data . get ( " username " )
user = self . get_login_user ( username )
user = self . get_login_user ( username )
host = validated_data [ ' host ' ]
host = validated_data [ " host " ]
challenge = validated_data [ ' challenge ' ]
challenge = validated_data [ " challenge " ]
action = validated_data [ ' action ' ]
action = validated_data [ " action " ]
salt = bytes ( user . userinfo . salt )
salt = bytes ( user . userinfo . salt )
enc_key = self . get_encryption_key ( salt )
enc_key = self . get_encryption_key ( salt )
@ -702,17 +700,17 @@ class AuthenticationViewSet(viewsets.ViewSet):
challenge_data = msgpack_decode ( box . decrypt ( challenge ) )
challenge_data = msgpack_decode ( box . decrypt ( challenge ) )
now = int ( datetime . now ( ) . timestamp ( ) )
now = int ( datetime . now ( ) . timestamp ( ) )
if action != expected_action :
if action != expected_action :
content = { ' code ' : ' wrong_action ' , ' detail ' : ' Expected " {} " but got something else ' . format ( expected_action ) }
content = { " code " : " wrong_action " , " detail " : ' Expected " {} " but got something else ' . format ( expected_action ) }
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
elif now - challenge_data [ ' timestamp ' ] > app_settings . CHALLENGE_VALID_SECONDS :
elif now - challenge_data [ " timestamp " ] > app_settings . CHALLENGE_VALID_SECONDS :
content = { ' code ' : ' challenge_expired ' , ' detail ' : ' Login challange has expired ' }
content = { " code " : " challenge_expired " , " detail " : " Login challange has expired " }
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
elif challenge_data [ ' userId ' ] != user . id :
elif challenge_data [ " userId " ] != user . id :
content = { ' code ' : ' wrong_user ' , ' detail ' : ' This challenge is for the wrong user ' }
content = { " code " : " wrong_user " , " detail " : " This challenge is for the wrong user " }
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
elif not settings . DEBUG and host . split ( ' : ' , 1 ) [ 0 ] != request . get_host ( ) :
elif not settings . DEBUG and host . split ( " : " , 1 ) [ 0 ] != request . get_host ( ) :
detail = ' Found wrong host name. Got: " {} " expected: " {} " ' . format ( host , request . get_host ( ) )
detail = ' Found wrong host name. Got: " {} " expected: " {} " ' . format ( host , request . get_host ( ) )
content = { ' code ' : ' wrong_host ' , ' detail ' : detail }
content = { " code " : " wrong_host " , " detail " : detail }
return Response ( content , status = status . HTTP_400_BAD_REQUEST )
return Response ( content , status = status . HTTP_400_BAD_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 )
@ -720,22 +718,24 @@ class AuthenticationViewSet(viewsets.ViewSet):
try :
try :
verify_key . verify ( response_raw , signature )
verify_key . verify ( response_raw , signature )
except nacl . exceptions . BadSignatureError :
except nacl . exceptions . BadSignatureError :
return Response ( { ' code ' : ' login_bad_signature ' , ' detail ' : ' Wrong password for user. ' } ,
return Response (
status = status . HTTP_401_UNAUTHORIZED )
{ " code " : " login_bad_signature " , " detail " : " Wrong password for user. " } ,
status = status . HTTP_401_UNAUTHORIZED ,
)
return None
return None
@action_decorator ( detail = False , methods = [ ' GET ' ] )
@action_decorator ( detail = False , methods = [ " GET " ] )
def is_etebase ( self , request , * args , * * kwargs ) :
def is_etebase ( self , request , * args , * * kwargs ) :
return Response ( { } , status = status . HTTP_200_OK )
return Response ( { } , status = status . HTTP_200_OK )
@action_decorator ( detail = False , methods = [ ' POST ' ] )
@action_decorator ( detail = False , methods = [ " POST " ] )
def login_challenge ( self , request , * args , * * kwargs ) :
def login_challenge ( self , request , * args , * * kwargs ) :
from datetime import datetime
from datetime import datetime
serializer = AuthenticationLoginChallengeSerializer ( data = request . data )
serializer = AuthenticationLoginChallengeSerializer ( data = request . data )
serializer . is_valid ( raise_exception = True )
serializer . is_valid ( raise_exception = True )
username = serializer . validated_data . get ( ' username ' )
username = serializer . validated_data . get ( " username " )
user = self . get_login_user ( username )
user = self . get_login_user ( username )
salt = bytes ( user . userinfo . salt )
salt = bytes ( user . userinfo . salt )
@ -755,25 +755,26 @@ class AuthenticationViewSet(viewsets.ViewSet):
}
}
return Response ( ret , status = status . HTTP_200_OK )
return Response ( ret , status = status . HTTP_200_OK )
@action_decorator ( detail = False , methods = [ ' POST ' ] )
@action_decorator ( detail = False , methods = [ " POST " ] )
def login ( self , request , * args , * * kwargs ) :
def login ( self , request , * args , * * kwargs ) :
outer_serializer = AuthenticationLoginSerializer ( data = request . data )
outer_serializer = AuthenticationLoginSerializer ( data = request . data )
outer_serializer . is_valid ( raise_exception = True )
outer_serializer . is_valid ( raise_exception = True )
response_raw = outer_serializer . validated_data [ ' response ' ]
response_raw = outer_serializer . validated_data [ " response " ]
response = msgpack_decode ( response_raw )
response = msgpack_decode ( response_raw )
signature = outer_serializer . validated_data [ ' signature ' ]
signature = outer_serializer . validated_data [ " signature " ]
context = { ' host ' : request . get_host ( ) }
context = { " host " : request . get_host ( ) }
serializer = AuthenticationLoginInnerSerializer ( data = response , context = context )
serializer = AuthenticationLoginInnerSerializer ( data = response , context = context )
serializer . is_valid ( raise_exception = True )
serializer . is_valid ( raise_exception = True )
bad_login_response = self . validate_login_request (
bad_login_response = self . validate_login_request (
request , serializer . validated_data , response_raw , signature , " login " )
request , serializer . validated_data , response_raw , signature , " login "
)
if bad_login_response is not None :
if bad_login_response is not None :
return bad_login_response
return bad_login_response
username = serializer . validated_data . get ( ' username ' )
username = serializer . validated_data . get ( " username " )
user = self . get_login_user ( username )
user = self . get_login_user ( username )
data = self . login_response_data ( user )
data = self . login_response_data ( user )
@ -782,27 +783,28 @@ class AuthenticationViewSet(viewsets.ViewSet):
return Response ( data , status = status . HTTP_200_OK )
return Response ( data , status = status . HTTP_200_OK )
@action_decorator ( detail = False , methods = [ ' POST ' ] , permission_classes = [ IsAuthenticated ] )
@action_decorator ( detail = False , methods = [ " POST " ] , permission_classes = [ IsAuthenticated ] )
def logout ( self , request , * args , * * kwargs ) :
def logout ( self , request , * args , * * kwargs ) :
request . auth . delete ( )
request . auth . delete ( )
user_logged_out . send ( sender = request . user . __class__ , request = request , user = request . user )
user_logged_out . send ( sender = request . user . __class__ , request = request , user = request . user )
return Response ( status = status . HTTP_204_NO_CONTENT )
return Response ( status = status . HTTP_204_NO_CONTENT )
@action_decorator ( detail = False , methods = [ ' POST ' ] , permission_classes = BaseViewSet . permission_classes )
@action_decorator ( detail = False , methods = [ " POST " ] , permission_classes = BaseViewSet . permission_classes )
def change_password ( self , request , * args , * * kwargs ) :
def change_password ( self , request , * args , * * kwargs ) :
outer_serializer = AuthenticationLoginSerializer ( data = request . data )
outer_serializer = AuthenticationLoginSerializer ( data = request . data )
outer_serializer . is_valid ( raise_exception = True )
outer_serializer . is_valid ( raise_exception = True )
response_raw = outer_serializer . validated_data [ ' response ' ]
response_raw = outer_serializer . validated_data [ " response " ]
response = msgpack_decode ( response_raw )
response = msgpack_decode ( response_raw )
signature = outer_serializer . validated_data [ ' signature ' ]
signature = outer_serializer . validated_data [ " signature " ]
context = { ' host ' : request . get_host ( ) }
context = { " host " : request . get_host ( ) }
serializer = AuthenticationChangePasswordInnerSerializer ( request . user . userinfo , data = response , context = context )
serializer = AuthenticationChangePasswordInnerSerializer ( request . user . userinfo , data = response , context = context )
serializer . is_valid ( raise_exception = True )
serializer . is_valid ( raise_exception = True )
bad_login_response = self . validate_login_request (
bad_login_response = self . validate_login_request (
request , serializer . validated_data , response_raw , signature , " changePassword " )
request , serializer . validated_data , response_raw , signature , " changePassword "
)
if bad_login_response is not None :
if bad_login_response is not None :
return bad_login_response
return bad_login_response
@ -810,35 +812,32 @@ class AuthenticationViewSet(viewsets.ViewSet):
return Response ( { } , status = status . HTTP_200_OK )
return Response ( { } , status = status . HTTP_200_OK )
@action_decorator ( detail = False , methods = [ ' POST ' ] , permission_classes = [ IsAuthenticated ] )
@action_decorator ( detail = False , methods = [ " POST " ] , permission_classes = [ IsAuthenticated ] )
def dashboard_url ( self , request , * args , * * kwargs ) :
def dashboard_url ( self , request , * args , * * kwargs ) :
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 EtebaseValidationError ( ' not_supported ' , ' This server doesn \' t have a user dashboard. ' ,
raise EtebaseValidationError (
status_code = status . HTTP_400_BAD_REQUEST )
" not_supported " , " This server doesn ' t have a user dashboard. " , status_code = status . HTTP_400_BAD_REQUEST
)
ret = {
ret = {
' url ' : get_dashboard_url ( request , * args , * * kwargs ) ,
" url " : get_dashboard_url ( request , * args , * * kwargs ) ,
}
}
return Response ( ret )
return Response ( ret )
class TestAuthenticationViewSet ( viewsets . ViewSet ) :
class TestAuthenticationViewSet ( viewsets . ViewSet ) :
allowed_methods = [ ' POST ' ]
allowed_methods = [ " POST " ]
renderer_classes = BaseViewSet . renderer_classes
renderer_classes = BaseViewSet . renderer_classes
parser_classes = BaseViewSet . parser_classes
parser_classes = BaseViewSet . parser_classes
def get_serializer_context ( self ) :
def get_serializer_context ( self ) :
return {
return { " request " : self . request , " format " : self . format_kwarg , " view " : self }
' request ' : self . request ,
' format ' : self . format_kwarg ,
' view ' : self
}
def list ( self , request , * args , * * kwargs ) :
def list ( self , request , * args , * * kwargs ) :
return Response ( status = status . HTTP_405_METHOD_NOT_ALLOWED )
return Response ( status = status . HTTP_405_METHOD_NOT_ALLOWED )
@action_decorator ( detail = False , methods = [ ' POST ' ] )
@action_decorator ( detail = False , methods = [ " POST " ] )
def reset ( self , request , * args , * * kwargs ) :
def reset ( self , request , * args , * * kwargs ) :
# Only run when in DEBUG mode! It's only used for tests
# Only run when in DEBUG mode! It's only used for tests
if not settings . DEBUG :
if not settings . DEBUG :
@ -846,13 +845,13 @@ class TestAuthenticationViewSet(viewsets.ViewSet):
with transaction . atomic ( ) :
with transaction . atomic ( ) :
user_queryset = get_user_queryset ( User . objects . all ( ) , self )
user_queryset = get_user_queryset ( User . objects . all ( ) , self )
user = get_object_or_404 ( user_queryset , username = request . data . get ( ' user ' ) . get ( ' username ' ) )
user = get_object_or_404 ( user_queryset , username = request . data . get ( " user " ) . get ( " username " ) )
# Only allow test users for extra safety
# Only allow test users for extra safety
if not getattr ( user , User . USERNAME_FIELD ) . startswith ( ' test_user ' ) :
if not getattr ( user , User . USERNAME_FIELD ) . startswith ( " test_user " ) :
return HttpResponseBadRequest ( " Endpoint not allowed for user. " )
return HttpResponseBadRequest ( " Endpoint not allowed for user. " )
if hasattr ( user , ' userinfo ' ) :
if hasattr ( user , " userinfo " ) :
user . userinfo . delete ( )
user . userinfo . delete ( )
serializer = AuthenticationSignupSerializer ( data = request . data , context = self . get_serializer_context ( ) )
serializer = AuthenticationSignupSerializer ( data = request . data , context = self . get_serializer_context ( ) )