From 3625039d4778b10755fd3ab9ccf8067860c28a40 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 26 Mar 2013 16:57:08 -0400 Subject: [PATCH] Start of group support. --- lib/main/models/__init__.py | 30 +++++++---- lib/main/tests/inventory.py | 104 ++++++++++++++++++++++++++++++------ lib/main/views.py | 33 ++++++++++++ lib/urls.py | 4 ++ 4 files changed, 145 insertions(+), 26 deletions(-) diff --git a/lib/main/models/__init__.py b/lib/main/models/__init__.py index 2b1926581e..fc137cdf6b 100644 --- a/lib/main/models/__init__.py +++ b/lib/main/models/__init__.py @@ -142,7 +142,7 @@ class PrimordialModel(models.Model): active = models.BooleanField(default=True) def __unicode__(self): - return unicode(self.name) + return unicode("%s-%s"% (self.name, self.id)) @classmethod def can_user_administrate(cls, user, obj): @@ -284,6 +284,7 @@ class Inventory(CommonModel): class Meta: app_label = 'main' verbose_name_plural = _('inventories') + unique_together = (("name", "organization"),) organization = models.ForeignKey(Organization, null=False, related_name='inventories') @@ -291,12 +292,6 @@ class Inventory(CommonModel): import lib.urls return reverse(lib.urls.views_InventoryDetail, args=(self.pk,)) - def __unicode__(self): - if self.organization: - return u'%s (%s)' % (self.name, self.organization) - else: - return self.name - @classmethod def _has_permission_types(cls, user, obj, allowed): if user.is_superuser: @@ -370,6 +365,7 @@ class Host(CommonModelNameNotUnique): class Meta: app_label = 'main' + unique_together = (("name", "inventory"),) inventory = models.ForeignKey('Inventory', null=False, related_name='hosts') @@ -395,6 +391,7 @@ class Group(CommonModelNameNotUnique): class Meta: app_label = 'main' + unique_together = (("name", "inventory"),) inventory = models.ForeignKey('Inventory', null=False, related_name='groups') parents = models.ManyToManyField('self', symmetrical=False, related_name='children', blank=True) @@ -410,10 +407,14 @@ class Group(CommonModelNameNotUnique): inventory = Inventory.objects.get(pk=data['inventory']) return Inventory._has_permission_types(user, inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE) + def get_absolute_url(self): + import lib.urls + return reverse(lib.urls.views_GroupsDetail, args=(self.pk,)) + # FIXME: audit nullables # FIXME: audit cascades -class VariableData(CommonModel): +class VariableData(CommonModelNameNotUnique): ''' A set of host or group variables ''' @@ -421,6 +422,7 @@ class VariableData(CommonModel): class Meta: app_label = 'main' verbose_name_plural = _('variable data') + unique_together = (("host", "group"),) host = models.ForeignKey('Host', null=True, default=None, blank=True, on_delete=SET_NULL, related_name='variable_data') group = models.ForeignKey('Group', null=True, default=None, blank=True, on_delete=SET_NULL, related_name='variable_data') @@ -526,8 +528,16 @@ class Permission(CommonModelNameNotUnique): # the project parameter is not used when dealing with READ, WRITE, or ADMIN permissions. permission_type = models.CharField(max_length=64, choices=PERMISSION_TYPE_CHOICES) - - + + def __unicode__(self): + return unicode("Permission(name=%s,ON(user=%s,team=%s),FOR(project=%s,inventory=%s,type=%s))" % ( + self.name, + self.user, + self.team, + self.project, + self.inventory, + self.permission_type + )) # TODO: other job types (later) diff --git a/lib/main/tests/inventory.py b/lib/main/tests/inventory.py index 45e8a49035..f029b376d7 100644 --- a/lib/main/tests/inventory.py +++ b/lib/main/tests/inventory.py @@ -129,13 +129,12 @@ class InventoryTest(BaseTest): new_host_b = dict(name='asdf1.example.com', inventory=inv.pk) new_host_c = dict(name='asdf2.example.com', inventory=inv.pk) new_host_d = dict(name='asdf3.example.com', inventory=inv.pk) - # FIXME: should raise 400 not 201, look into required fields in rest_framework - print hosts + new_host_e = dict(name='asdf4.example.com', inventory=inv.pk) data0 = self.post(hosts, data=invalid, expect=400, auth=self.get_super_credentials()) data0 = self.post(hosts, data=new_host_a, expect=201, auth=self.get_super_credentials()) # an org admin can add hosts - data1 = self.post(hosts, data=new_host_a, expect=201, auth=self.get_normal_credentials()) + data1 = self.post(hosts, data=new_host_e, expect=201, auth=self.get_normal_credentials()) # a normal user cannot add hosts data2 = self.post(hosts, data=new_host_b, expect=403, auth=self.get_nobody_credentials()) @@ -143,24 +142,46 @@ class InventoryTest(BaseTest): # a normal user with inventory edit permissions (on any inventory) can create hosts edit_perm = Permission.objects.create( user = self.other_django_user, - inventory = Inventory.objects.get(pk=1), + inventory = Inventory.objects.get(pk=inv.pk), permission_type = PERM_INVENTORY_WRITE ) data3 = self.post(hosts, data=new_host_c, expect=201, auth=self.get_other_credentials()) - # hostnames must be unique -- posting a duplicate just returns the previous - data4 = self.post(hosts, data=new_host_c, expect=200, auth=self.get_other_credentials()) - self.assertEqual(data1['id'], data4['id']) + # hostnames must be unique inside an organization + data4 = self.post(hosts, data=new_host_c, expect=400, auth=self.get_other_credentials()) - # a super user can add groups + ########################################### + # GROUPS - # an org admin can create groups + invalid = dict(name='web1') + new_group_a = dict(name='web2', inventory=inv.pk) + new_group_b = dict(name='web3', inventory=inv.pk) + new_group_c = dict(name='web4', inventory=inv.pk) + new_group_d = dict(name='web5', inventory=inv.pk) + new_group_e = dict(name='web6', inventory=inv.pk) + data0 = self.post(groups, data=invalid, expect=400, auth=self.get_super_credentials()) + data0 = self.post(groups, data=new_group_a, expect=201, auth=self.get_super_credentials()) - # a normal user cannot create groups + # an org admin can add hosts + data1 = self.post(groups, data=new_group_e, expect=201, auth=self.get_normal_credentials()) - # a normal user with inventory edit permissions can create groups - - # group names must be unique for each inventory record + # a normal user cannot add hosts + data2 = self.post(groups, data=new_group_b, expect=403, auth=self.get_nobody_credentials()) + + # a normal user with inventory edit permissions (on any inventory) can create hosts + # already done! + #edit_perm = Permission.objects.create( + # user = self.other_django_user, + # inventory = Inventory.objects.get(pk=inv.pk), + # permission_type = PERM_INVENTORY_WRITE + #) + data3 = self.post(groups, data=new_group_c, expect=201, auth=self.get_other_credentials()) + + # hostnames must be unique inside an organization + data4 = self.post(groups, data=new_group_c, expect=400, auth=self.get_other_credentials()) + + ################################################# + # HOSTS->inventories # a super user can associate hosts with inventories @@ -170,13 +191,19 @@ class InventoryTest(BaseTest): # a normal user with edit permission on the inventory can associate hosts with inventories + ################################################## + # GROUPS->inventories + # a super user can associate groups with inventories # an org admin can associate groups with inventories - # a normal user cannot associate hosts with inventories + # a normal user cannot associate groups with inventories - # a normal user with edit permissions on the inventory can associate hosts with inventories + # a normal user with edit permissions on the inventory can associate groups with inventories + + ################################################### + # VARIABLES # a super user can create variable objects @@ -186,6 +213,9 @@ class InventoryTest(BaseTest): # a normal user with at least one inventory edit permission can create variable objects + ################################################### + # VARIABLES -> GROUPS + # a super user can associate variable objects with groups # an org admin can associate variable objects with groups @@ -194,6 +224,9 @@ class InventoryTest(BaseTest): # a normal user with inventory edit permissions can associate variable objects with groups + #################################################### + # VARIABLES -> HOSTS + # a super user can associate variable objects with hosts # an org admin can associate variable objects with hosts @@ -202,6 +235,9 @@ class InventoryTest(BaseTest): # a normal user with inventory edit permissions can associate variable objects with hosts + #################################################### + # SUBGROUPS + # a super user can set subgroups # an org admin can set subgroups @@ -210,6 +246,9 @@ class InventoryTest(BaseTest): # a normal user with inventory edit permissions can associate subgroups + ###################################################### + # GROUP ACCESS + # a super user can get a group record # an org admin can get a group record @@ -218,6 +257,9 @@ class InventoryTest(BaseTest): # a regular user cannot read any group records + ######################################################## + # HOST ACCESS + # a super user can get a host record # an org admin can get a host record @@ -226,13 +268,19 @@ class InventoryTest(BaseTest): # a regular user cannot get a host record + ######################################################### + # GROUP VARIABLE ACCESS + # a super user can see the variables attached to a group # a org admin can see the variables attached to a group # a user who is on a team who has read permissions on an inventory can see the variables attached to a group - # a regular user cannot get a host record + # a regular user cannot get a group variable record + + ######################################################### + # HOST VARIABLE ACCESS # a super user can see the variables attached to a host @@ -241,6 +289,9 @@ class InventoryTest(BaseTest): # a user who is on a team who has read permissions on an inventory can see the variables attached to a host # a regular user cannot see variables attached to a host + + ######################################################### + # GROUP CHILDREN ACCESS # a super user can see the children attached to a group @@ -249,6 +300,9 @@ class InventoryTest(BaseTest): # a user who is on a team who has read permissions on an inventory can see the children attached to a group # a regular user cannot see children attached to a group + + ######################################################### + # VARIABLE RESOURCE ACCESS # a super user can see a variable record @@ -257,6 +311,9 @@ class InventoryTest(BaseTest): # a user who is on a team who has read permissions on an inventory can see the variable record # a regular user cannot see a variable record + + ######################################################### + # SUPER USER DISASSOCIATION # a super user can disassociate... @@ -267,6 +324,9 @@ class InventoryTest(BaseTest): # subgroups from groups # the inventory task code returns valid inventory JSON. + + ######################################################### + # ORG ADMIN DISASSOCIATION # an org admin user can disassociate... @@ -276,6 +336,9 @@ class InventoryTest(BaseTest): # subgroups from groups + ######################################################### + # USER DISASSOCIATION + # a user with inventory edit permission disassociate... # hosts from inventory @@ -283,6 +346,9 @@ class InventoryTest(BaseTest): # groups from inventory # subgroups from groups + + ######################################################### + # USER DISASSOCIATION (2) # a regular user cannot disassociate.... @@ -292,6 +358,9 @@ class InventoryTest(BaseTest): # subgroups from inventory + ######################################################### + # TAGS + # the following objects can be tagged # inventory @@ -322,6 +391,9 @@ class InventoryTest(BaseTest): # variable records + ######################################################### + # RELATED FIELDS + # on an inventory resource, I can see related resources for hosts and groups and permissions # and these work diff --git a/lib/main/views.py b/lib/main/views.py index 1554087699..2f6f5516f9 100644 --- a/lib/main/views.py +++ b/lib/main/views.py @@ -315,4 +315,37 @@ class HostsDetail(BaseDetail): serializer_class = HostSerializer permission_classes = (CustomRbac,) +class GroupsList(BaseList): + + model = Group + serializer_class = GroupSerializer + permission_classes = (CustomRbac,) + + def _get_queryset(self): + ''' + I can see groups when: + I'm a superuser, + or an organization admin of an inventory they are in + or when I have allowing read permissions via a user or team on an inventory they are in + ''' + base = Groups.objects + if self.request.user.is_superuser: + return base.all() + admin_of = base.filter(inventory__organization__admins__in = [ self.request.user ]).distinct() + has_user_perms = base.filter( + inventory__permissions__user__in = [ self.request.user ], + inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ, + ).distinct() + has_team_perms = base.filter( + inventory__permissions__team__in = self.request.user.teams.all(), + inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ, + ).distinct() + return admin_of | has_user_perms | has_team_perms + +class GroupsDetail(BaseDetail): + + model = Group + serializer_class = GroupSerializer + permission_classes = (CustomRbac,) + diff --git a/lib/urls.py b/lib/urls.py index ed1ba3aaf2..cb6dc58a56 100644 --- a/lib/urls.py +++ b/lib/urls.py @@ -48,6 +48,8 @@ views_InventoryList = views.InventoryList.as_view() views_InventoryDetail = views.InventoryDetail.as_view() # group service +views_GroupsList = views.GroupsList.as_view() +views_GroupsDetail = views.GroupsDetail.as_view() # host service views_HostsList = views.HostsList.as_view() @@ -99,6 +101,8 @@ urlpatterns = patterns('', url(r'^api/v1/hosts/(?P[0-9]+)/$', views_HostsDetail), # group service + url(r'^api/v1/groups/$', views_GroupsList), + url(r'^api/v1/groups/(?P[0-9]+)/$', views_GroupsDetail), # inventory variable service