def get_property(request, object_identifier, property_name): # Query strings: u = request.GET.get('u', None) # filter by user # Decode object_identifier and property_name: object_identifier = urlunquote(object_identifier) property_name = urlunquote(property_name) # Query: qs = Property.objects.filter( object__identifier=object_identifier, name=property_name) if u: qs = qs.filter(Q(user=None) | Q(user__username=u)) else: qs = qs.filter(user=None) # Check authorization: if request.user.has_perm('pstore.view_any_object'): pass elif not ObjectPerm.objects.filter( object__identifier=object_identifier, user=request.user).exists(): raise PermissionDenied('Not staff and not permitted') # Results: items = list(qs[0:2]) if not items: # Was this because we didn't have permission or because there # simply wasn't a property? if (request.user.has_perm('pstore.view_any_object') and not Property.objects.filter( object__identifier=object_identifier, name=property_name).exists()): raise Http404('No such property') raise PermissionDenied('Not staff or not permitted') if len(items) > 1: class_ = Property.MultipleObjectsReturned raise class_('get() returned more than one Property. Lookup parameters' ' were %r' % ({'object': object_identifier, 'name': property_name},)) # TODO: if the value is really large, we should somehow write it to disk # before passing it around. property = items[0] file = BytesIO(property.value) # Response: return EncryptedResponse(fp=file, enctype=property.enctype())
def update_properties(request, object_identifier): if len(request.GET) != 0: raise NotImplementedError('Unexpected GET args', request.GET) if len(request.POST) != 1: # only the nonce_b64 should be here raise NotImplementedError('Unexpected POST args', request.POST) # Decode object_identifier: object_identifier = urlunquote(object_identifier) # Check authorization and existence: if request.user.has_perm('pstore.view_any_object'): obj = get_object_or_404(Object, identifier=object_identifier) else: obj = get_object_or_403(Object, identifier=object_identifier) try: ObjectPerm.objects.get(object=obj, user=request.user, can_write=True) except ObjectPerm.DoesNotExist: raise PermissionDenied('No permissions for user', request.user) # Fetch users and do a few basic checks on them. Below, during the Property # create loop, we'll do some more checks. usernames = [] for property_name in request.FILES.keys(): for file in request.FILES.getlist(property_name): username = file.name usernames.append(username) usernames = set(usernames) users = dict([(i.username, i) for i in User.objects.filter(username__in=usernames)]) if len(users) != len(usernames): raise Exception('FIXME-EXCEPTION: user count invalid') # XXX: assert that user is in usernames! # TODO: do more assertions on the users here.. # TODO: add to audit log the attempted move of userset X to userset Y # Fetch files and re-set properties. Here we won't accept transparent # switches of public to shared properties. for property_name in request.FILES.keys(): # TODO: ensure that we have a file for every user for every key # TODO: check public/shared properties for swapperony Property.objects.filter(object=obj, name=property_name).delete() for file in request.FILES.getlist(property_name): # We only add new shared (encrypted) properties, we don't need to # touch the public ones. user = users[file.name] create_property(object=obj, property=property_name, file=file, user=user) # Update permissions. For now, we'll only use the can_write, since we don't # have a means of communicating that state yet. # TODO: (a) audit log here? # TODO: (b) don't delete permitted users and add a created column to # ObjectPerm? # TODO: (c) double check that there are no user-properties for disallowed # users left? currently_allowed = set([i.user for i in (ObjectPerm.objects.filter(object=obj) .select_related('user'))]) del currently_allowed # fixme.. use this # XXX: take currently_allowed, remove now-allowed: # delete the leftovers # add the not-in-currently_allowed ObjectPerm.objects.filter(object=obj).delete() for user in users.values(): ObjectPerm.objects.create(object=obj, user=user, can_write=True) return VoidResponse()
def set_property(request, object_identifier, property_name): author = request.user # FIXME # Check the rest of the arguments. if len(request.GET) != 0: raise NotImplementedError('Unexpected GET args', request.GET) if set(request.POST.keys()) - set(['nonce_b64', 'o_excl']): raise NotImplementedError('Unexpected POST args', request.POST) o_excl = (request.POST.get('o_excl', '') == '1') # force create or fail # Docode URI arguments. object_identifier = urlunquote(object_identifier) property_name = urlunquote(property_name) # Which users? usernames = [i.name for i in request.FILES.getlist(property_name)] assert usernames # Are they the-public-user? if usernames == ['*']: is_public = True users = None users_dict = {'*': None} # the public-user elif all(i != '*' for i in usernames): is_public = False users = list(User.objects.filter(username__in=usernames)) if len(users) != len(usernames): raise Exception('FIXME-EXCEPTION') users_dict = dict((i.username, i) for i in users) else: raise Exception('FIXME-EXCEPTION') # Autocreate object if it didn't exist yet. Next, make *sure* that 'author' # that we found in the nonce is a valid writer. if users: obj, created = Object.objects.get_or_create( identifier=object_identifier) else: try: obj = Object.objects.get(identifier=object_identifier) except Object.DoesNotExist: raise Http404('Refusing to create an object automatically') else: created = False if created: # We need a list of users if this is a new entry! Yes.. that means # that we *must* have an encrypted property for the machine to be # created. Let that be the case for now. assert users # Author must be in the list of users too. if author not in users: raise PermissionDenied('Author is not in list of users', author.username) # Give everyone admin perms for now. We shall devise some kind of # scheme to tell admins from readers apart in the future. for user in users: ObjectPerm.objects.create(object=obj, user=user, can_write=True) else: if o_excl: raise PermissionDenied('Object exists already') # Check that author has write powers. # TODO: we could allow SU-users to do stuff.. if not obj.allowed.filter(can_write=True, user=author).exists(): raise PermissionDenied('Author has no write powers for object', author.username) if not is_public: # The amount of received files must obviously be equal to the # number of allowed users. Checks against the individual files is # done below. We compare the user list to make sure we don't get # too few or too many. if set(i.user for i in obj.allowed.all()) != set(users): raise PermissionDenied('Object exists already and supplied ' 'list of users differs') # TODO: extra audit stuff here. do more? oldies = list(Property.objects.filter(object=obj, name=property_name)[0:1]) if oldies: if oldies[0].type == Property.TYPE_PUBLIC and not is_public: # LOG: switching property from public to shared pass elif oldies[0].type == Property.TYPE_SHARED and is_public: # LOG: switching property from shared to public pass # Purge the old properties.. Property.objects.filter(object=obj, name=property_name).delete() # ... and create new ones. for file in request.FILES.getlist(property_name): user = users_dict[file.name] create_property(object=obj, property=property_name, file=file, user=user) return VoidResponse()
def get_object(request, object_identifier): """ Get a single object. You get verbose info. Note that if you want info about the properties for a different user, you'll need to redo the query with that user id. """ # Decode object_identifier. object_identifier = urlunquote(object_identifier) # Check authorization and existence: u = request.GET.get('u', None) if request.user.has_perm('object.view_any_object'): obj = get_object_or_404(Object, identifier=object_identifier) elif u != request.user.username: raise PermissionDenied() else: obj = get_object_or_403(Object, identifier=object_identifier) # Check if the user is allowed to view this. try: obj.allowed.get(user=request.user) except ObjectDoesNotExist: if not request.user.has_perm('object.view_any_object'): raise PermissionDenied() # Get lots of info for this object. result = {} # Get the allowed users. allowed = obj.allowed.select_related('user') result['users'] = dict([(i.user.username, {'can_write': i.can_write}) for i in allowed]) # Get a list of properties. propqs = Property.objects.filter(object=obj) if u: propqs = propqs.filter(Q(user__username=u) | Q(user=None)) else: propqs = propqs.filter(user=None) # BEWARE: For the SQLite3 backend, the LENGTH() is incorrect! propqs = propqs.extra(select={'size': 'LENGTH(value)'}) properties = set(propqs.values_list('id', 'name', 'type', 'size', 'user')) # Get the values, but only for small properties. small_property_ids = [i[0] for i in properties if i[3] <= 2048] # https://code.djangoproject.com/ticket/9619 # We cannot do values_list when using SQLite3. # #property_values = dict(Property.objects # # .filter(id__in=small_property_ids) # # .values_list('id', 'value')) property_qs = Property.objects.filter(id__in=small_property_ids) property_values = dict((i.id, i.value) for i in property_qs.only('id', 'value')) # Put properties in the results dictionary. result['properties'] = {} enctype, encuid = None, None # cache encryption type for property_id, name, type, size, user in properties: assert type in (Property.TYPE_PUBLIC, Property.TYPE_SHARED) # Cache the enctype so we don't have to look it up for every property. if user: if not enctype: encuid = user enctype = PublicKey.objects.get(user__id=user).key_type() assert enctype else: assert encuid == user, '%r == %r' % (encuid, user) # These are always returned. info = {'data': None, 'size': size, 'enctype': ('none', enctype)[bool(user)]} # If data is small enough, it is set too. if property_id in property_values: info['data'] = b64encode(property_values[property_id]) result['properties'][name] = info # If a superuser called and wants to know what properties there are, add # those. if not u: # Fetch property names for the properties. for name, type in (Property.objects.filter(object=obj) .exclude(user=None) .values_list('name', 'type').distinct()): assert type == Property.TYPE_SHARED # These are always returned, but this time we have no useful info # for the caller. info = {'data': None, 'size': None, 'enctype': None} result['properties'][name] = info return JsonResponse(request, result)