diff options
author | Leif Johansson <leifj@sunet.se> | 2010-07-10 01:25:36 +0200 |
---|---|---|
committer | Leif Johansson <leifj@sunet.se> | 2010-07-10 01:25:36 +0200 |
commit | e5c38904d1d7e01781c6622ae5ec3d902494deff (patch) | |
tree | d7939ed27c0754e79ee62b4dda0afddcc45c2b60 /coip | |
parent | 54563defc38d0075bfc448720e4f5805998a824a (diff) |
edit+delete+create for names along with linking and an acl mechanism
Diffstat (limited to 'coip')
-rw-r--r-- | coip/apps/auth/views.py | 4 | ||||
-rw-r--r-- | coip/apps/name/admin.py | 5 | ||||
-rw-r--r-- | coip/apps/name/forms.py | 22 | ||||
-rw-r--r-- | coip/apps/name/models.py | 92 | ||||
-rw-r--r-- | coip/apps/name/views.py | 72 | ||||
-rw-r--r-- | coip/urls.py | 2 |
6 files changed, 174 insertions, 23 deletions
diff --git a/coip/apps/auth/views.py b/coip/apps/auth/views.py index 28ff7de..094e974 100644 --- a/coip/apps/auth/views.py +++ b/coip/apps/auth/views.py @@ -53,8 +53,8 @@ def accounts_login_federated(request): profile.save() #autocreate a few personal namespaces - lookup('user:'+profile.identifier,True,'#l '+request.user+'#rw') - lookup(request.user,True,'#l '+request.user+'#rw') + lookup('user:'+profile.identifier,True,'system:anyuser#l '+request.user+'#rw') + lookup(request.user,True,'system:anyuser#l '+request.user+'#rw') next = request.session.get("after_login_redirect", None) if next is not None: diff --git a/coip/apps/name/admin.py b/coip/apps/name/admin.py index 477da76..54fc99e 100644 --- a/coip/apps/name/admin.py +++ b/coip/apps/name/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from coip.apps.name.models import Name, Attribute +from coip.apps.name.models import Name, Attribute, NameLink admin.site.register(Name) -admin.site.register(Attribute)
\ No newline at end of file +admin.site.register(Attribute) +admin.site.register(NameLink)
\ No newline at end of file diff --git a/coip/apps/name/forms.py b/coip/apps/name/forms.py index 62c6fb5..47c7795 100644 --- a/coip/apps/name/forms.py +++ b/coip/apps/name/forms.py @@ -4,7 +4,8 @@ Created on Jun 24, 2010 @author: leifj ''' from django import forms -from coip.apps.name.models import Name, Attribute +from coip.apps.name.models import Name, Attribute, NameLink +from django.forms.fields import BooleanField class NameForm(forms.ModelForm): class Meta: @@ -17,4 +18,21 @@ class AttributeForm(forms.ModelForm): class NameEditForm(forms.ModelForm): class Meta: model = Name - fields = ['short','description']
\ No newline at end of file + fields = ['short','description'] + +class NewNameForm(forms.ModelForm): + class Meta: + model = Name + fields = ['type','value','short','description'] + +class NameDeleteForm(forms.Form): + recursive = BooleanField(label="Also delete all nodes below this node?",required=False) + confirm = BooleanField(label="Confirm") + +class NameLinkForm(forms.ModelForm): + class Meta: + model = NameLink + fields = ['dst','type','data'] + +class NameLinkDeleteForm(forms.Form): + confirm = BooleanField(label="Confirm")
\ No newline at end of file diff --git a/coip/apps/name/models.py b/coip/apps/name/models.py index d68b8bb..3a1e2cc 100644 --- a/coip/apps/name/models.py +++ b/coip/apps/name/models.py @@ -7,6 +7,8 @@ from django.db import models import re from twisted.python.reflect import ObjectNotFound from pprint import pprint +from django.contrib.auth.models import User +from django.core.exceptions import ObjectDoesNotExist class Attribute(models.Model): name = models.CharField(unique=True,max_length=255) @@ -24,9 +26,9 @@ class Name(models.Model): type = models.ForeignKey(Attribute, blank=True, null=True,related_name='names') value = models.CharField(max_length=255) parent = models.ForeignKey('self', blank=True, null=True,related_name='children') - partof = models.ForeignKey('self', blank=True, null=True,related_name='parts') acl = models.TextField(blank=True) # fully-qualified-name '#' rights short = models.CharField(max_length=64,blank=True) + creator = models.ForeignKey(User,blank=True, null=True) description = models.TextField(blank=True) timecreated = models.DateTimeField(auto_now_add=True) lastupdated = models.DateTimeField(auto_now=True) @@ -61,12 +63,76 @@ class Name(models.Model): return str + def remove(self,recursive=False): + if recursive: + for c in self.children.all(): + c.remove(recursive) + self.delete() + + # TODO: placeholder for more complete acl system + def copy_acl(self): + return self.acl + + def link(self,dst,type,data): + if not self.has_link(dst,NameLink.part_of,data): + link = NameLink(src=self,dst=dst,type=type,data=data) + link.save() + + def unlink(self,dst,type,data): + try: + link = NameLink.objects.get(src=self,dst=dst,type=type,data=data) + link.delete() + except ObjectDoesNotExist: + pass + + def has_link(self,dst,type,data): + return NameLink.objects.filter(src=self,dst=dst,type=type,data=data).count() > 0 + + def add_ace(self,name,perm): + self.link(name,NameLink.access_control,perm) + + def del_ace(self,name,perm): + self.unlink(name,NameLink.access_control,perm) + + def list_acl(self): + return NameLink.objects.filter(src=self,type=NameLink.access_control) + + def add_partof(self,part): + self.link(part,NameLink.part_of,None) + def has_permission(self,user,perm): - return True + pprint("has_permission %s %s %s" % (self,user,perm)) + # TODO: reverse order of test for production system - will spead-up superuser-test and it is cheap + #pprint(NameLink.objects.filter(src=self,type=NameLink.access_control,data=perm,dst__memberships__user=user)) + # user is superuser or acl is on implicit group or user is member of acl group + anyuser = lookup("system:anyuser",True) + if NameLink.objects.filter(src=self,dst=anyuser,type=NameLink.access_control,data__contains=perm).count() > 0: + return True + if NameLink.objects.filter(src=self,type=NameLink.access_control,data__contains=perm,dst__memberships__user=user).count() > 0: + return True + + if user.is_superuser: + return False + + return False #user.is_superuser def permitted_children(self,user,perm): return filter(lambda s: s.has_permission(user,perm),self.children.all()) +class NameLink(models.Model): + src = models.ForeignKey(Name,related_name='sources') + dst = models.ForeignKey(Name,related_name='destinations') + type = models.IntegerField() + data = models.CharField(max_length=255,blank=True,null=True) + timecreated = models.DateTimeField(auto_now_add=True) + lastupdated = models.DateTimeField(auto_now=True) + + access_control = 0 + part_of = 1 + + def __unicode__(self): + return "%s -> %s [%s %s]" % (self.src,self.dst,self.type,self.data) + def roots(): return Name.objects.filter(parent=None) @@ -76,7 +142,7 @@ def _traverse(name,callable,user,depth): else: t = callable(name,depth) if depth > 0: - children = [_traverse(s,callable,user,depth - 1) for s in name.permitted_children(user,'#l')] + children = [_traverse(s,callable,user,depth - 1) for s in name.permitted_children(user,'l')] if children: t['children'] = children return t @@ -88,14 +154,15 @@ def traverse(name,callable,user,depth,includeroot=False): if includeroot: t = callable(name,depth) if depth > 0: - children = [_traverse(s,callable,user,depth - 1) for s in name.permitted_children(user,'#l')] + children = [_traverse(s,callable,user,depth - 1) for s in name.permitted_children(user,'l')] if children: t['children'] = children return t else: - return [_traverse(s,callable,user,depth - 1) for s in name.permitted_children(user,'#l')] + return [_traverse(s,callable,user,depth - 1) for s in name.permitted_children(user,'l')] -def walkto(root,nameparts,autocreate=False,autoacl='#l'): +# TODO - remove system user dependency +def walkto(root,nameparts,autocreate=False,autoacl='l'): name = None for n in nameparts: (a,eq,v) = n.partition('=') @@ -103,22 +170,25 @@ def walkto(root,nameparts,autocreate=False,autoacl='#l'): attribute = Attribute.objects.get(name=a) try: name = Name.objects.get(parent=root,type=attribute.id,value=v) - except ObjectNotFound,e: + except ObjectDoesNotExist,e: if autocreate: - name = Name.objects.create(parent=root,type=attribute.id,value=v,acl=autoacl) + name = Name(parent=root,creator=User.objects.get(username='system'),type=attribute.id,value=v,acl=autoacl) + name.save() else: raise e else: try: name = Name.objects.get(parent=root,type=None,value=a) - except ObjectNotFound,e: + except ObjectDoesNotExist,e: if autocreate: - name = Name.objects.create(parent=root,type=None,value=a,acl=autoacl) + name = Name(parent=root,creator=User.objects.get(username='system'),type=None,value=a,acl=autoacl) + name.save() else: raise e + root = name return name -def lookup(name,autocreate=False,autoacl='#l'): +def lookup(name,autocreate=False,autoacl='l'): return walkto(None,nameparts=re.compile('[;:]').split(name),autocreate=autocreate,autoacl=autoacl) def attribute(a): diff --git a/coip/apps/name/views.py b/coip/apps/name/views.py index 6e0132f..a59190b 100644 --- a/coip/apps/name/views.py +++ b/coip/apps/name/views.py @@ -10,9 +10,69 @@ from django.http import HttpResponseNotFound, HttpResponseForbidden,\ from django.contrib.auth.decorators import login_required from coip.multiresponse import respond_to, json_response from pprint import pprint -from coip.apps.name.forms import NameEditForm +from coip.apps.name.forms import NameEditForm, NewNameForm, NameDeleteForm from twisted.python.reflect import ObjectNotFound +def delete(request,id): + name = None + try: + name = Name.objects.get(id=id) + except ObjectNotFound: + return HttpResponseNotFound() + + if not name.has_permission(request.user,'d'): + return HttpResponseForbidden() + + if request.method == 'POST': + form = NameDeleteForm(request.POST) + if form.is_valid(): + if not form.cleaned_data['confirm']: + return HttpResponseRedirect("/name/id/%d" % name.id) + + parent = name.parent + if not form.cleaned_data['recursive'] and name.children.count() > 0: + return HttpResponseForbidden("Will not delete non-empty node") + + if form.cleaned_data['recursive']: + name.remove(True) + else: + name.remove(False) + + if parent: + return HttpResponseRedirect("/name/id/%d" % parent.id) + else: + return HttpResponseRedirect("/name"); + else: + form = NameDeleteForm() + + return respond_to(request,{'text/html': 'apps/name/edit.html'},{'form': form,'name': name,'formtitle': 'Remove name confirmation' ,'submitname': 'Delete'}) + +def add(request,id): + parent = None + if id: + try: + parent = Name.objects.get(id=id) + except ObjectNotFound: + return HttpResponseNotFound() + + if id: + if not parent.has_permission(request.user,'i'): + return HttpResponseForbidden('You are not allowed to create names under '+parent) + else: + if not request.user.admin: + return HttpResponseForbidden('You are not allowed to create names') + + if request.method == 'POST': + name = Name(parent=parent,creator=request.user,acl=parent.copy_acl()) + form = NewNameForm(request.POST,instance=name) + if form.is_valid(): + form.save() + return HttpResponseRedirect("/name/id/%d" % name.id) + else: + form = NewNameForm() + + return respond_to(request,{'text/html': 'apps/name/edit.html'},{'form': form,'name': parent,'formtitle': 'Create new name','submitname': 'Create'}) + def edit(request,id): name = None try: @@ -20,7 +80,7 @@ def edit(request,id): except ObjectNotFound: return HttpResponseNotFound() - if not name.has_permission(request.user,'#w'): + if not name.has_permission(request.user,'w'): return HttpResponseForbidden() if request.method == 'POST': @@ -31,7 +91,7 @@ def edit(request,id): else: form = NameEditForm(instance=name) - return respond_to(request,{'text/html': 'apps/name/edit.html'},{'form': form,'name': name}) + return respond_to(request,{'text/html': 'apps/name/edit.html'},{'form': form,'name': name,'formtitle': 'Change name','submitname': 'Update'}) def show_root(request): @@ -48,9 +108,9 @@ def show(request,name): {'text/html': 'apps/name/name.html'}, {'name': name, 'memberships': name.memberships, - 'delete': name.has_permission(request.user,'#d'), - 'insert': name.has_permission(request.user,'#i'), - 'edit': name.has_permission(request.user,'#w')}) + 'delete': name.has_permission(request.user,'d'), + 'insert': name.has_permission(request.user,'i'), + 'edit': name.has_permission(request.user,'w')}) else: return HttpResponseForbidden() diff --git a/coip/urls.py b/coip/urls.py index af60b15..03426ee 100644 --- a/coip/urls.py +++ b/coip/urls.py @@ -29,6 +29,8 @@ urlpatterns = patterns('', (r'^name/(?P<name>[^0-9\/]+)$', 'coip.apps.name.views.show_by_name'), (r'^name$', 'coip.apps.name.views.show_by_name'), (r'^name/edit/id/(?P<id>[0-9]+)$', 'coip.apps.name.views.edit'), + (r'^name/delete/id/(?P<id>[0-9]+)$', 'coip.apps.name.views.delete'), + (r'^name/add/id/(?P<id>[0-9]+)$', 'coip.apps.name.views.add'), # JSON Tree (r'^ctree.json$', 'coip.apps.name.views.ctree'), (r'^ctree/(?P<id>[0-9]+).json$', 'coip.apps.name.views.ctree'), |