diff --git a/awx/api/generics.py b/awx/api/generics.py index 93dd3ba444..a660b2acba 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -26,6 +26,7 @@ from rest_framework import views # AWX from awx.main.models import * # noqa from awx.main.utils import * # noqa +from awx.api.serializers import ResourceAccessListElementSerializer __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView', 'ListCreateAPIView', 'SubListAPIView', 'SubListCreateAPIView', @@ -33,6 +34,7 @@ __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView', 'RetrieveUpdateAPIView', 'RetrieveDestroyAPIView', 'RetrieveUpdateDestroyAPIView', 'DestroyAPIView', 'SubDetailAPIView', + 'ResourceAccessList', 'ParentMixin',] logger = logging.getLogger('awx.api.generics') @@ -473,3 +475,20 @@ class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, RetrieveDestroyAPIView class DestroyAPIView(GenericAPIView, generics.DestroyAPIView): pass + + +class ResourceAccessList(ListAPIView): + + serializer_class = ResourceAccessListElementSerializer + + def get_queryset(self): + self.object_id = self.kwargs['pk'] + resource_model = getattr(self, 'resource_model') + obj = resource_model.objects.get(pk=self.object_id) + + roles = set([p.role for p in obj.role_permissions.all()]) + ancestors = set() + for r in roles: + ancestors.update(set(r.ancestors.all())) + return User.objects.filter(roles__in=list(ancestors)) + diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 80defe4d8f..f58a4af1c8 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -238,7 +238,7 @@ class BaseSerializer(serializers.ModelSerializer): __metaclass__ = BaseSerializerMetaclass class Meta: - fields = ('id', 'type', 'resource_id', 'url', 'related', 'summary_fields', 'created', + fields = ('id', 'type', 'url', 'related', 'summary_fields', 'created', 'modified', 'name', 'description') summary_fields = () # FIXME: List of field names from this serializer that should be used when included as part of another's summary_fields. summarizable_fields = () # FIXME: List of field names on this serializer that should be included in summary_fields. @@ -253,7 +253,6 @@ class BaseSerializer(serializers.ModelSerializer): created = serializers.SerializerMethodField() modified = serializers.SerializerMethodField() active = serializers.SerializerMethodField() - resource_id = serializers.SerializerMethodField() def get_type(self, obj): @@ -293,9 +292,6 @@ class BaseSerializer(serializers.ModelSerializer): res['created_by'] = reverse('api:user_detail', args=(obj.created_by.pk,)) if getattr(obj, 'modified_by', None) and obj.modified_by.is_active: res['modified_by'] = reverse('api:user_detail', args=(obj.modified_by.pk,)) - if isinstance(obj, ResourceMixin): - content_type_id = ContentType.objects.get_for_model(obj).pk - res['resource_access_list'] = reverse('api:resource_access_list', kwargs={'content_type_id': content_type_id, 'pk': obj.pk}) return res def _get_summary_fields(self, obj): @@ -366,11 +362,6 @@ class BaseSerializer(serializers.ModelSerializer): summary_fields['roles'] = roles return summary_fields - def get_resource_id(self, obj): - content_type_id = ContentType.objects.get_for_model(obj).pk - return '%d/%d' % (content_type_id, obj.pk) - return None - def get_created(self, obj): if obj is None: return None @@ -545,8 +536,6 @@ class BaseSerializer(serializers.ModelSerializer): def to_representation(self, obj): ret = super(BaseSerializer, self).to_representation(obj) - if 'resource_id' in ret and ret['resource_id'] is None: - ret.pop('resource_id') return ret @@ -817,6 +806,7 @@ class UserSerializer(BaseSerializer): credentials = reverse('api:user_credentials_list', args=(obj.pk,)), roles = reverse('api:user_roles_list', args=(obj.pk,)), activity_stream = reverse('api:user_activity_stream_list', args=(obj.pk,)), + access_list = reverse('api:user_access_list', args=(obj.pk,)), )) return res @@ -871,6 +861,7 @@ class OrganizationSerializer(BaseSerializer): notifiers_any = reverse('api:organization_notifiers_any_list', args=(obj.pk,)), notifiers_success = reverse('api:organization_notifiers_success_list', args=(obj.pk,)), notifiers_error = reverse('api:organization_notifiers_error_list', args=(obj.pk,)), + access_list = reverse('api:organization_access_list', args=(obj.pk,)), )) return res @@ -943,6 +934,7 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer): notifiers_any = reverse('api:project_notifiers_any_list', args=(obj.pk,)), notifiers_success = reverse('api:project_notifiers_success_list', args=(obj.pk,)), notifiers_error = reverse('api:project_notifiers_error_list', args=(obj.pk,)), + access_list = reverse('api:project_access_list', args=(obj.pk,)), )) # Backwards compatibility. if obj.current_update: @@ -1042,6 +1034,7 @@ class InventorySerializer(BaseSerializerWithVariables): job_templates = reverse('api:inventory_job_template_list', args=(obj.pk,)), scan_job_templates = reverse('api:inventory_scan_job_template_list', args=(obj.pk,)), ad_hoc_commands = reverse('api:inventory_ad_hoc_commands_list', args=(obj.pk,)), + access_list = reverse('api:inventory_access_list', args=(obj.pk,)), #single_fact = reverse('api:inventory_single_fact_view', args=(obj.pk,)), )) if obj.organization and obj.organization.active: @@ -1212,6 +1205,7 @@ class GroupSerializer(BaseSerializerWithVariables): activity_stream = reverse('api:group_activity_stream_list', args=(obj.pk,)), inventory_sources = reverse('api:group_inventory_sources_list', args=(obj.pk,)), ad_hoc_commands = reverse('api:group_ad_hoc_commands_list', args=(obj.pk,)), + access_list = reverse('api:group_access_list', args=(obj.pk,)), #single_fact = reverse('api:group_single_fact_view', args=(obj.pk,)), )) if obj.inventory and obj.inventory.active: @@ -1475,6 +1469,7 @@ class TeamSerializer(BaseSerializer): credentials = reverse('api:team_credentials_list', args=(obj.pk,)), roles = reverse('api:team_roles_list', args=(obj.pk,)), activity_stream = reverse('api:team_activity_stream_list', args=(obj.pk,)), + access_list = reverse('api:team_access_list', args=(obj.pk,)), )) if obj.organization and obj.organization.active: res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,)) @@ -1509,37 +1504,13 @@ class RoleSerializer(BaseSerializer): return ret -""" -class ResourceSerializer(BaseSerializer): - - class Meta: - model = Resource - fields = ('*',) - - def get_related(self, obj): - ret = super(ResourceSerializer, self).get_related(obj) - ret['access_list'] = reverse('api:resource_access_list', args=(obj.pk,)) - try: - if obj.content_object: - ret.update(reverseGenericForeignKey(obj.content_object)) - except AttributeError as e: - print(e) - # AttributeError's happen if our content_object is pointing at - # a model that no longer exists. This is dirty data and ideally - # doesn't exist, but in case it does, let's not puke. - pass - - return ret - -""" class ResourceAccessListElementSerializer(UserSerializer): def to_representation(self, user): ret = super(ResourceAccessListElementSerializer, self).to_representation(user) - content_type = ContentType.objects.get(pk=self.context['view'].content_type_id) object_id = self.context['view'].object_id - obj = content_type.model_class().objects.get(pk=object_id) + obj = self.context['view'].resource_model.objects.get(pk=object_id) if 'summary_fields' not in ret: ret['summary_fields'] = {} @@ -1611,7 +1582,8 @@ class CredentialSerializer(BaseSerializer): def get_related(self, obj): res = super(CredentialSerializer, self).get_related(obj) res.update(dict( - activity_stream = reverse('api:credential_activity_stream_list', args=(obj.pk,)) + activity_stream = reverse('api:credential_activity_stream_list', args=(obj.pk,)), + access_list = reverse('api:credential_access_list', args=(obj.pk,)), )) if obj.user: res['user'] = reverse('api:user_detail', args=(obj.user.pk,)) @@ -1690,6 +1662,7 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer): notifiers_any = reverse('api:job_template_notifiers_any_list', args=(obj.pk,)), notifiers_success = reverse('api:job_template_notifiers_success_list', args=(obj.pk,)), notifiers_error = reverse('api:job_template_notifiers_error_list', args=(obj.pk,)), + access_list = reverse('api:job_template_access_list', args=(obj.pk,)), )) if obj.host_config_key: res['callback'] = reverse('api:job_template_callback', args=(obj.pk,)) diff --git a/awx/api/urls.py b/awx/api/urls.py index 3af0ee74d2..8b07352343 100644 --- a/awx/api/urls.py +++ b/awx/api/urls.py @@ -24,6 +24,7 @@ organization_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/notifiers_any/$', 'organization_notifiers_any_list'), url(r'^(?P[0-9]+)/notifiers_error/$', 'organization_notifiers_error_list'), url(r'^(?P[0-9]+)/notifiers_success/$', 'organization_notifiers_success_list'), + url(r'^(?P[0-9]+)/access_list/$', 'organization_access_list'), ) user_urls = patterns('awx.api.views', @@ -36,6 +37,7 @@ user_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/credentials/$', 'user_credentials_list'), url(r'^(?P[0-9]+)/roles/$', 'user_roles_list'), url(r'^(?P[0-9]+)/activity_stream/$', 'user_activity_stream_list'), + url(r'^(?P[0-9]+)/access_list/$', 'user_access_list'), ) project_urls = patterns('awx.api.views', @@ -51,6 +53,7 @@ project_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/notifiers_any/$', 'project_notifiers_any_list'), url(r'^(?P[0-9]+)/notifiers_error/$', 'project_notifiers_error_list'), url(r'^(?P[0-9]+)/notifiers_success/$', 'project_notifiers_success_list'), + url(r'^(?P[0-9]+)/access_list/$', 'project_access_list'), ) project_update_urls = patterns('awx.api.views', @@ -68,6 +71,7 @@ team_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/credentials/$', 'team_credentials_list'), url(r'^(?P[0-9]+)/roles/$', 'team_roles_list'), url(r'^(?P[0-9]+)/activity_stream/$', 'team_activity_stream_list'), + url(r'^(?P[0-9]+)/access_list/$', 'team_access_list'), ) inventory_urls = patterns('awx.api.views', @@ -84,6 +88,7 @@ inventory_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/job_templates/$', 'inventory_job_template_list'), url(r'^(?P[0-9]+)/scan_job_templates/$', 'inventory_scan_job_template_list'), url(r'^(?P[0-9]+)/ad_hoc_commands/$', 'inventory_ad_hoc_commands_list'), + url(r'^(?P[0-9]+)/access_list/$', 'inventory_access_list'), #url(r'^(?P[0-9]+)/single_fact/$', 'inventory_single_fact_view'), ) @@ -117,6 +122,7 @@ group_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/activity_stream/$', 'group_activity_stream_list'), url(r'^(?P[0-9]+)/inventory_sources/$', 'group_inventory_sources_list'), url(r'^(?P[0-9]+)/ad_hoc_commands/$', 'group_ad_hoc_commands_list'), + url(r'^(?P[0-9]+)/access_list/$', 'group_access_list'), #url(r'^(?P[0-9]+)/single_fact/$', 'group_single_fact_view'), ) @@ -150,6 +156,7 @@ credential_urls = patterns('awx.api.views', url(r'^$', 'credential_list'), url(r'^(?P[0-9]+)/activity_stream/$', 'credential_activity_stream_list'), url(r'^(?P[0-9]+)/$', 'credential_detail'), + url(r'^(?P[0-9]+)/access_list/$', 'credential_access_list'), # See also credentials resources on users/teams. ) @@ -162,15 +169,6 @@ role_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/children/$', 'role_children_list'), ) -resource_urls = patterns('awx.api.views', - #url(r'^$', 'resource_list'), - #url(r'^(?P[0-9]+)/$', 'resource_detail'), - url(r'^(?P[0-9]+)/(?P[0-9]+)/access_list/$', 'resource_access_list'), - #url(r'^(?P[0-9]+)/users/$', 'resource_users_list'), - #url(r'^(?P[0-9]+)/teams/$', 'resource_teams_list'), - #url(r'^(?P[0-9]+)/roles/$', 'resource_teams_list'), -) - job_template_urls = patterns('awx.api.views', url(r'^$', 'job_template_list'), url(r'^(?P[0-9]+)/$', 'job_template_detail'), @@ -183,6 +181,7 @@ job_template_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/notifiers_any/$', 'job_template_notifiers_any_list'), url(r'^(?P[0-9]+)/notifiers_error/$', 'job_template_notifiers_error_list'), url(r'^(?P[0-9]+)/notifiers_success/$', 'job_template_notifiers_success_list'), + url(r'^(?P[0-9]+)/access_list/$', 'job_template_access_list'), ) job_urls = patterns('awx.api.views', @@ -292,7 +291,6 @@ v1_urls = patterns('awx.api.views', url(r'^inventory_scripts/', include(inventory_script_urls)), url(r'^credentials/', include(credential_urls)), url(r'^roles/', include(role_urls)), - url(r'^resources/', include(resource_urls)), url(r'^job_templates/', include(job_template_urls)), url(r'^jobs/', include(job_urls)), url(r'^job_host_summaries/', include(job_host_summary_urls)), diff --git a/awx/api/views.py b/awx/api/views.py index df4bad17a1..959b4dd2cf 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -703,6 +703,12 @@ class OrganizationNotifiersSuccessList(SubListCreateAttachDetachAPIView): parent_model = Organization relationship = 'notifiers_success' +class OrganizationAccessList(ResourceAccessList): + + model = User # needs to be User for AccessLists's + resource_model = Organization + new_in_300 = True + class TeamList(ListCreateAPIView): model = Team @@ -783,6 +789,11 @@ class TeamActivityStreamList(SubListAPIView): Q(credential__in=parent.credentials.all()) | Q(permission__in=parent.permissions.all())) +class TeamAccessList(ResourceAccessList): + + model = User # needs to be User for AccessLists's + resource_model = Team + new_in_300 = True class ProjectList(ListCreateAPIView): @@ -947,6 +958,12 @@ class ProjectUpdateNotificationsList(SubListAPIView): parent_model = Project relationship = 'notifications' +class ProjectAccessList(ResourceAccessList): + + model = User # needs to be User for AccessLists's + resource_model = Project + new_in_300 = True + class UserList(ListCreateAPIView): model = User @@ -1086,6 +1103,12 @@ class UserDetail(RetrieveUpdateDestroyAPIView): own_credential.mark_inactive() return super(UserDetail, self).destroy(request, *args, **kwargs) +class UserAccessList(ResourceAccessList): + + model = User # needs to be User for AccessLists's + resource_model = User + new_in_300 = True + class CredentialList(ListCreateAPIView): model = Credential @@ -1114,6 +1137,11 @@ class CredentialActivityStreamList(SubListAPIView): # Okay, let it through. return super(type(self), self).get(request, *args, **kwargs) +class CredentialAccessList(ResourceAccessList): + + model = User # needs to be User for AccessLists's + resource_model = Credential + new_in_300 = True class InventoryScriptList(ListCreateAPIView): @@ -1174,6 +1202,12 @@ class InventoryActivityStreamList(SubListAPIView): qs = self.request.user.get_queryset(self.model) return qs.filter(Q(inventory=parent) | Q(host__in=parent.hosts.all()) | Q(group__in=parent.groups.all())) +class InventoryAccessList(ResourceAccessList): + + model = User # needs to be User for AccessLists's + resource_model = Inventory + new_in_300 = True + class InventoryJobTemplateList(SubListAPIView): model = JobTemplate @@ -1509,6 +1543,13 @@ class GroupDetail(RetrieveUpdateDestroyAPIView): obj.mark_inactive_recursive() return Response(status=status.HTTP_204_NO_CONTENT) +class GroupAccessList(ResourceAccessList): + + model = User # needs to be User for AccessLists's + resource_model = Group + new_in_300 = True + + class InventoryGroupsList(SubListCreateAttachDetachAPIView): model = Group @@ -2186,6 +2227,12 @@ class JobTemplateJobsList(SubListCreateAPIView): relationship = 'jobs' parent_key = 'job_template' +class JobTemplateAccessList(ResourceAccessList): + + model = User # needs to be User for AccessLists's + resource_model = JobTemplate + new_in_300 = True + class SystemJobTemplateList(ListAPIView): model = SystemJobTemplate @@ -3268,49 +3315,6 @@ class RoleChildrenList(SubListAPIView): role = Role.objects.get(pk=self.kwargs['pk']) return role.children -''' -class ResourceDetail(RetrieveAPIView): - - model = Resource - serializer_class = ResourceSerializer - permission_classes = (IsAuthenticated,) - new_in_300 = True - - # XXX: Permissions - only roles the user has access to see should be listed here - def get_queryset(self): - return Resource.objects - -class ResourceList(ListAPIView): - - model = Resource - serializer_class = ResourceSerializer - permission_classes = (IsAuthenticated,) - new_in_300 = True - - def get_queryset(self): - return Resource.objects.filter(permissions__role__ancestors__members=self.request.user) - -''' - -class ResourceAccessList(ListAPIView): - - model = User - serializer_class = ResourceAccessListElementSerializer - permission_classes = (IsAuthenticated,) - new_in_300 = True - - def get_queryset(self): - self.content_type_id = self.kwargs['content_type_id'] - self.object_id = self.kwargs['pk'] - #resource = Resource.objects.get(pk=self.kwargs['pk']) - content_type = ContentType.objects.get(pk=self.content_type_id) - obj = content_type.model_class().objects.get(pk=self.object_id) - - roles = set([p.role for p in obj.role_permissions.all()]) - ancestors = set() - for r in roles: - ancestors.update(set(r.ancestors.all())) - return User.objects.filter(roles__in=list(ancestors))