1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-02 01:21:21 +03:00

AC-905 Work in progress on better filtering of inactive/deleted objects.

This commit is contained in:
Chris Church 2014-02-06 16:52:09 -05:00
parent f579dbd72d
commit 9807477944
4 changed files with 222 additions and 64 deletions

View File

@ -14,7 +14,7 @@ __all__ = ['__version__']
try:
import awx.devonly
MODE = 'development'
except ImportError:
except ImportError: # pragma: no cover
MODE = 'production'
def find_commands(management_dir):
@ -27,7 +27,7 @@ def find_commands(management_dir):
continue
elif f.endswith('.py') and f[:-3] not in commands:
commands.append(f[:-3])
elif f.endswith('.pyc') and f[:-4] not in commands:
elif f.endswith('.pyc') and f[:-4] not in commands: # pragma: no cover
commands.append(f[:-4])
except OSError:
pass
@ -43,7 +43,7 @@ def prepare_env():
# Hide DeprecationWarnings when running in production. Need to first load
# settings to apply our filter after Django's own warnings filter.
from django.conf import settings
if not settings.DEBUG:
if not settings.DEBUG: # pragma: no cover
warnings.simplefilter('ignore', DeprecationWarning)
# Monkeypatch Django find_commands to also work with .pyc files.
import django.core.management
@ -53,7 +53,7 @@ def prepare_env():
import django.utils
try:
import django.utils.six
except ImportError:
except ImportError: # pragma: no cover
import six
sys.modules['django.utils.six'] = sys.modules['six']
django.utils.six = sys.modules['django.utils.six']
@ -61,7 +61,7 @@ def prepare_env():
# Use the AWX_TEST_DATABASE_* environment variables to specify the test
# database settings to use when management command is run as an external
# program via unit tests.
for opt in ('ENGINE', 'NAME', 'USER', 'PASSWORD', 'HOST', 'PORT'):
for opt in ('ENGINE', 'NAME', 'USER', 'PASSWORD', 'HOST', 'PORT'): # pragma: no cover
if os.environ.get('AWX_TEST_DATABASE_%s' % opt, None):
settings.DATABASES['default'][opt] = os.environ['AWX_TEST_DATABASE_%s' % opt]
# Disable capturing all SQL queries in memory when in DEBUG mode.
@ -75,7 +75,7 @@ def manage():
prepare_env()
# Now run the command (or display the version).
from django.core.management import execute_from_command_line
if len(sys.argv) >= 2 and sys.argv[1] in ('version', '--version'):
if len(sys.argv) >= 2 and sys.argv[1] in ('version', '--version'): # pragma: no cover
sys.stdout.write('%s\n' % __version__)
else:
execute_from_command_line(sys.argv)

View File

@ -143,8 +143,10 @@ class BaseSerializer(serializers.ModelSerializer):
def get_related(self, obj):
res = SortedDict()
if getattr(obj, 'created_by', None):
if getattr(obj, 'created_by', None) and obj.created_by.is_active:
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,))
return res
def get_summary_fields(self, obj):
@ -154,12 +156,17 @@ class BaseSerializer(serializers.ModelSerializer):
for fk, related_fields in SUMMARIZABLE_FK_FIELDS.items():
try:
fkval = getattr(obj, fk, None)
if fkval is not None:
summary_fields[fk] = SortedDict()
for field in related_fields:
fval = getattr(fkval, field, None)
if fval is not None:
summary_fields[fk][field] = fval
if fkval is None:
continue
if hasattr(fkval, 'active') and not fkval.active:
continue
if hasattr(fkval, 'is_active') and not fkval.is_active:
continue
summary_fields[fk] = SortedDict()
for field in related_fields:
fval = getattr(fkval, field, None)
if fval is not None:
summary_fields[fk][field] = fval
# Can be raised by the reverse accessor for a OneToOneField.
except ObjectDoesNotExist:
pass
@ -337,7 +344,7 @@ class ProjectSerializer(BaseSerializer):
project_updates = reverse('api:project_updates_list', args=(obj.pk,)),
activity_stream = reverse('api:project_activity_stream_list', args=(obj.pk,)),
))
if obj.credential:
if obj.credential and obj.credential.active:
res['credential'] = reverse('api:credential_detail',
args=(obj.credential.pk,))
if obj.current_update:
@ -364,6 +371,12 @@ class ProjectSerializer(BaseSerializer):
raise serializers.ValidationError('Invalid path choice')
return attrs
def to_native(self, obj):
ret = super(ProjectSerializer, self).to_native(obj)
if 'credential' in ret and (not obj.credential or not obj.credential.active):
ret['credential'] = None
return ret
class ProjectPlaybooksSerializer(ProjectSerializer):
@ -433,12 +446,19 @@ class InventorySerializer(BaseSerializerWithVariables):
variable_data = reverse('api:inventory_variable_data', args=(obj.pk,)),
script = reverse('api:inventory_script_view', args=(obj.pk,)),
tree = reverse('api:inventory_tree_view', args=(obj.pk,)),
organization = reverse('api:organization_detail', args=(obj.organization.pk,)),
inventory_sources = reverse('api:inventory_inventory_sources_list', args=(obj.pk,)),
activity_stream = reverse('api:inventory_activity_stream_list', args=(obj.pk,)),
))
if obj.organization and obj.organization.active:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,))
return res
def to_native(self, obj):
ret = super(InventorySerializer, self).to_native(obj)
if 'organization' in ret and (not obj.organization or not obj.organization.active):
ret['organization'] = None
return ret
class HostSerializer(BaseSerializerWithVariables):
@ -458,7 +478,6 @@ class HostSerializer(BaseSerializerWithVariables):
res = super(HostSerializer, self).get_related(obj)
res.update(dict(
variable_data = reverse('api:host_variable_data', args=(obj.pk,)),
inventory = reverse('api:inventory_detail', args=(obj.inventory.pk,)),
groups = reverse('api:host_groups_list', args=(obj.pk,)),
all_groups = reverse('api:host_all_groups_list', args=(obj.pk,)),
job_events = reverse('api:host_job_events_list', args=(obj.pk,)),
@ -466,9 +485,11 @@ class HostSerializer(BaseSerializerWithVariables):
activity_stream = reverse('api:host_activity_stream_list', args=(obj.pk,)),
#inventory_sources = reverse('api:host_inventory_sources_list', args=(obj.pk,)),
))
if obj.last_job:
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,))
if obj.last_job and obj.last_job.active:
res['last_job'] = reverse('api:job_detail', args=(obj.last_job.pk,))
if obj.last_job_host_summary:
if obj.last_job_host_summary and obj.last_job_host_summary.job.active:
res['last_job_host_summary'] = reverse('api:job_host_summary_detail', args=(obj.last_job_host_summary.pk,))
return res
@ -543,6 +564,16 @@ class HostSerializer(BaseSerializerWithVariables):
return attrs
def to_native(self, obj):
ret = super(HostSerializer, self).to_native(obj)
if 'inventory' in ret and (not obj.inventory or not obj.inventory.active):
ret['inventory'] = None
if 'last_job' in ret and (not obj.last_job or not obj.last_job.active):
ret['last_job'] = None
if 'last_job_host_summary' in ret and (not obj.last_job_host_summary or not obj.last_job_host_summary.job.active):
ret['last_job_host_summary'] = None
return ret
class GroupSerializer(BaseSerializerWithVariables):
@ -563,13 +594,15 @@ class GroupSerializer(BaseSerializerWithVariables):
potential_children = reverse('api:group_potential_children_list', args=(obj.pk,)),
children = reverse('api:group_children_list', args=(obj.pk,)),
all_hosts = reverse('api:group_all_hosts_list', args=(obj.pk,)),
inventory = reverse('api:inventory_detail', args=(obj.inventory.pk,)),
job_events = reverse('api:group_job_events_list', args=(obj.pk,)),
job_host_summaries = reverse('api:group_job_host_summaries_list', args=(obj.pk,)),
inventory_source = reverse('api:inventory_source_detail', args=(obj.inventory_source.pk,)),
activity_stream = reverse('api:group_activity_stream_list', args=(obj.pk,)),
#inventory_sources = reverse('api:group_inventory_sources_list', args=(obj.pk,)),
))
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,))
if obj.inventory_source:
res['inventory_source'] = reverse('api:inventory_source_detail', args=(obj.inventory_source.pk,))
return res
def validate_name(self, attrs, source):
@ -578,6 +611,12 @@ class GroupSerializer(BaseSerializerWithVariables):
raise serializers.ValidationError('Invalid group name')
return attrs
def to_native(self, obj):
ret = super(GroupSerializer, self).to_native(obj)
if 'inventory' in ret and (not obj.inventory or not obj.inventory.active):
ret['inventory'] = None
return ret
class GroupTreeSerializer(GroupSerializer):
@ -659,11 +698,11 @@ class InventorySourceSerializer(BaseSerializer):
#hosts = reverse('api:inventory_source_hosts_list', args=(obj.pk,)),
#groups = reverse('api:inventory_source_groups_list', args=(obj.pk,)),
))
if obj.inventory:
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,))
if obj.group:
if obj.group and obj.group.active:
res['group'] = reverse('api:group_detail', args=(obj.group.pk,))
if obj.credential:
if obj.credential and obj.credential.active:
res['credential'] = reverse('api:credential_detail',
args=(obj.credential.pk,))
if obj.current_update:
@ -712,6 +751,16 @@ class InventorySourceSerializer(BaseSerializer):
field_opts['rax_region_choices'] = self.opts.model.get_rax_region_choices()
return metadata
def to_native(self, obj):
ret = super(InventorySourceSerializer, self).to_native(obj)
if 'inventory' in ret and (not obj.inventory or not obj.inventory.active):
ret['inventory'] = None
if 'group' in ret and (not obj.group or not obj.group.active):
ret['group'] = None
if 'credential' in ret and (not obj.credential or not obj.credential.active):
ret['credential'] = None
return ret
class InventoryUpdateSerializer(BaseTaskSerializer):
@ -749,12 +798,19 @@ class TeamSerializer(BaseSerializer):
projects = reverse('api:team_projects_list', args=(obj.pk,)),
users = reverse('api:team_users_list', args=(obj.pk,)),
credentials = reverse('api:team_credentials_list', args=(obj.pk,)),
organization = reverse('api:organization_detail', args=(obj.organization.pk,)),
permissions = reverse('api:team_permissions_list', args=(obj.pk,)),
activity_stream = reverse('api:team_activity_stream_list', args=(obj.pk,)),
))
if obj.organization:
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,))
return res
def to_native(self, obj):
ret = super(TeamSerializer, self).to_native(obj)
if 'organization' in ret and (not obj.organization or not obj.organization.active):
ret['organization'] = None
return ret
class PermissionSerializer(BaseSerializer):
@ -792,6 +848,18 @@ class PermissionSerializer(BaseSerializer):
'assigning deployment permissions')
return attrs
def to_native(self, obj):
ret = super(PermissionSerializer, self).to_native(obj)
if 'user' in ret and (not obj.user or not obj.user.is_active):
ret['user'] = None
if 'team' in ret and (not obj.team or not obj.team.active):
ret['team'] = None
if 'project' in ret and (not obj.project or not obj.project.active):
ret['project'] = None
if 'inventory' in ret and (not obj.inventory or not obj.inventory.active):
ret['inventory'] = None
return ret
class CredentialSerializer(BaseSerializer):
@ -810,6 +878,10 @@ class CredentialSerializer(BaseSerializer):
def to_native(self, obj):
ret = super(CredentialSerializer, self).to_native(obj)
if 'user' in ret and (not obj.user or not obj.user.is_active):
ret['user'] = None
if 'team' in ret and (not obj.team or not obj.team.active):
ret['team'] = None
# Replace the actual encrypted value with the string $encrypted$.
for field in Credential.PASSWORD_FIELDS:
if field in ret and unicode(ret[field]).startswith('$encrypted$'):
@ -852,20 +924,36 @@ class JobTemplateSerializer(BaseSerializer):
return {}
res = super(JobTemplateSerializer, self).get_related(obj)
res.update(dict(
inventory = reverse('api:inventory_detail', args=(obj.inventory.pk,)),
project = reverse('api:project_detail', args=(obj.project.pk,)),
jobs = reverse('api:job_template_jobs_list', args=(obj.pk,)),
jobs = reverse('api:job_template_jobs_list', args=(obj.pk,)),
activity_stream = reverse('api:job_template_activity_stream_list', args=(obj.pk,)),
))
if obj.credential:
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,))
if obj.project and obj.project.active:
res['project'] = reverse('api:project_detail', args=(obj.project.pk,)),
if obj.credential and obj.credential.active:
res['credential'] = reverse('api:credential_detail', args=(obj.credential.pk,))
if obj.cloud_credential:
if obj.cloud_credential and obj.cloud_credential.active:
res['cloud_credential'] = reverse('api:credential_detail',
args=(obj.cloud_credential.pk,))
if obj.host_config_key:
res['callback'] = reverse('api:job_template_callback', args=(obj.pk,))
return res
def to_native(self, obj):
ret = super(JobTemplateSerializer, self).to_native(obj)
if 'inventory' in ret and (not obj.inventory or not obj.inventory.active):
ret['inventory'] = None
if 'project' in ret and (not obj.project or not obj.project.active):
ret['project'] = None
if 'playbook' in ret:
ret['playbook'] = ''
if 'credential' in ret and (not obj.credential or not obj.credential.active):
ret['credential'] = None
if 'cloud_credential' in ret and (not obj.cloud_credential or not obj.cloud_credential.active):
ret['cloud_credential'] = None
return ret
def validate_playbook(self, attrs, source):
project = attrs.get('project', None)
playbook = attrs.get('playbook', '')
@ -894,16 +982,23 @@ class JobSerializer(BaseTaskSerializer):
return {}
res = super(JobSerializer, self).get_related(obj)
res.update(dict(
inventory = reverse('api:inventory_detail', args=(obj.inventory.pk,)),
project = reverse('api:project_detail', args=(obj.project.pk,)),
credential = reverse('api:credential_detail', args=(obj.credential.pk,)),
job_events = reverse('api:job_job_events_list', args=(obj.pk,)),
job_host_summaries = reverse('api:job_job_host_summaries_list', args=(obj.pk,)),
activity_stream = reverse('api:job_activity_stream_list', args=(obj.pk,)),
))
if obj.job_template:
res['job_template'] = reverse('api:job_template_detail', args=(obj.job_template.pk,))
if obj.cloud_credential:
if obj.job_template and obj.job_template.active:
res['job_template'] = reverse('api:job_template_detail',
args=(obj.job_template.pk,))
if obj.inventory and obj.inventory.active:
res['inventory'] = reverse('api:inventory_detail',
args=(obj.inventory.pk,))
if obj.project and obj.project.active:
res['project'] = reverse('api:project_detail',
args=(obj.project.pk,))
if obj.credential and obj.credential.active:
res['credential'] = reverse('api:credential_detail',
args=(obj.credential.pk,))
if obj.cloud_credential and obj.cloud_credential.active:
res['cloud_credential'] = reverse('api:credential_detail',
args=(obj.cloud_credential.pk,))
if obj.can_start or True:
@ -923,9 +1018,11 @@ class JobSerializer(BaseTaskSerializer):
return
# Don't auto-populate name or description.
data.setdefault('job_type', job_template.job_type)
data.setdefault('inventory', job_template.inventory.pk)
data.setdefault('project', job_template.project.pk)
data.setdefault('playbook', job_template.playbook)
if job_template.inventory:
data.setdefault('inventory', job_template.inventory.pk)
if job_template.project:
data.setdefault('project', job_template.project.pk)
data.setdefault('playbook', job_template.playbook)
if job_template.credential:
data.setdefault('credential', job_template.credential.pk)
if job_template.cloud_credential:
@ -937,6 +1034,22 @@ class JobSerializer(BaseTaskSerializer):
data.setdefault('job_tags', job_template.job_tags)
return super(JobSerializer, self).from_native(data, files)
def to_native(self, obj):
ret = super(JobSerializer, self).to_native(obj)
if 'job_template' in ret and (not obj.job_template or not obj.job_template.active):
ret['job_template'] = None
if 'inventory' in ret and (not obj.inventory or not obj.inventory.active):
ret['inventory'] = None
if 'project' in ret and (not obj.project or not obj.project.active):
ret['project'] = None
if 'playbook' in ret:
ret['playbook'] = ''
if 'credential' in ret and (not obj.credential or not obj.credential.active):
ret['credential'] = None
if 'cloud_credential' in ret and (not obj.cloud_credential or not obj.cloud_credential.active):
ret['cloud_credential'] = None
return ret
class JobHostSummarySerializer(BaseSerializer):

View File

@ -120,7 +120,7 @@ class BaseAccess(object):
return self.model.objects.none()
def can_read(self, obj):
return bool(obj and self.get_queryset().filter(pk=obj.pk).count())
return bool(obj and self.get_queryset().filter(pk=obj.pk).exists())
def can_add(self, data):
return self.user.is_superuser
@ -172,18 +172,18 @@ class UserAccess(BaseAccess):
qs = self.model.objects.filter(is_active=True).distinct()
if self.user.is_superuser:
return qs
if self.user.admin_of_organizations.count():
if self.user.admin_of_organizations.filter(active=True).exists():
return qs
return qs.filter(
Q(pk=self.user.pk) |
Q(organizations__in=self.user.admin_of_organizations.all()) |
Q(organizations__in=self.user.organizations.all()) |
Q(teams__in=self.user.teams.all())
Q(organizations__in=self.user.admin_of_organizations.filter(active=True)) |
Q(organizations__in=self.user.organizations.filter(active=True)) |
Q(teams__in=self.user.teams.filter(active=True))
).distinct()
def can_add(self, data):
return bool(self.user.is_superuser or
self.user.admin_of_organizations.count())
self.user.admin_of_organizations.filter(active=True).exists())
def can_change(self, obj, data):
# A user can be changed if they are themselves, or by org admins or
@ -195,7 +195,8 @@ class UserAccess(BaseAccess):
# Admin implies changing all user fields.
if self.user.is_superuser:
return True
return bool(obj.organizations.filter(admins__in=[self.user]).count())
#return bool(obj.organizations.filter(active=True, admins__in=[self.user]).exists()) TODO: replace line below
return bool(obj.organizations.filter(admins__in=[self.user]).exists())
def can_delete(self, obj):
if obj == self.user:
@ -206,7 +207,8 @@ class UserAccess(BaseAccess):
# cannot delete the last active superuser
return False
return bool(self.user.is_superuser or
obj.organizations.filter(admins__in=[self.user]).count())
#obj.organizations.filter(active=True, admins__in=[self.user]).exists()) TODO: replace line below
obj.organizations.filter(admins__in=[self.user]).exists())
class OrganizationAccess(BaseAccess):
'''
@ -258,21 +260,24 @@ class InventoryAccess(BaseAccess):
qs = qs.select_related('created_by', 'organization')
if self.user.is_superuser:
return qs
admin_of = qs.filter(organization__admins__in=[self.user]).distinct()
admin_of = qs.filter(organization__admins__in=[self.user],
#organization__active=True).distinct() TODO: enable this line
).distinct()
has_user_perms = qs.filter(
permissions__user__in=[self.user],
permissions__permission_type__in=allowed,
permissions__active=True,
permissions__active=True, # TODO: add test for this case
).distinct()
has_team_perms = qs.filter(
permissions__team__users__in=[self.user],
#permissions__team__active=True, TODO: enable this line
permissions__permission_type__in=allowed,
permissions__active=True,
permissions__active=True, # TODO: add test for this case
).distinct()
return admin_of | has_user_perms | has_team_perms
def has_permission_types(self, obj, allowed):
return bool(obj and self.get_queryset(allowed).filter(pk=obj.pk).count())
return bool(obj and self.get_queryset(allowed).filter(pk=obj.pk).exists())
def can_read(self, obj):
return self.has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_READ)
@ -280,7 +285,9 @@ class InventoryAccess(BaseAccess):
def can_add(self, data):
# If no data is specified, just checking for generic add permission?
if not data:
return bool(self.user.is_superuser or self.user.admin_of_organizations.count())
return bool(self.user.is_superuser or
#self.user.admin_of_organizations.filter(active=True).exists()) TODO: replace line below
self.user.admin_of_organizations.all().exists())
# Otherwise, verify that the user has access to change the parent
# organization of this inventory.
if self.user.is_superuser:
@ -360,7 +367,6 @@ class HostAccess(BaseAccess):
raise PermissionDenied("license has expired")
if validation_info.get('free_instances', 0) > 0:
# BOOKMARK
return True
instances = validation_info.get('available_instances', 0)
raise PermissionDenied("license range of %s instances has been exceeded" % instances)
@ -433,7 +439,6 @@ class GroupAccess(BaseAccess):
parent_pks.add(obj.pk)
child_pks = set(sub_obj.all_children.values_list('pk', flat=True))
child_pks.add(sub_obj.pk)
#print parent_pks, child_pks
if parent_pks & child_pks:
return False
return True
@ -518,11 +523,14 @@ class CredentialAccess(BaseAccess):
qs = qs.select_related('created_by', 'user', 'team')
if self.user.is_superuser:
return qs
#orgs_as_admin = self.user.admin_of_organizations.filter(active=True) TODO: replace line below
orgs_as_admin = self.user.admin_of_organizations.all()
return qs.filter(
Q(user=self.user) |
Q(user__organizations__in=orgs_as_admin) |
Q(user__admin_of_organizations__in=orgs_as_admin) |
#Q(team__organization__in=orgs_as_admin, team__active=True) | TODO: replace both lines below
#Q(team__users__in=[self.user], team__active=True)
Q(team__organization__in=orgs_as_admin) |
Q(team__users__in=[self.user])
)
@ -550,11 +558,14 @@ class CredentialAccess(BaseAccess):
if obj.user:
if self.user == obj.user:
return True
if obj.user.organizations.filter(admins__in=[self.user]).count():
#if obj.user.organizations.filter(active=True, admins__in=[self.user]).exists(): TODO: replace line below
if obj.user.organizations.filter(admins__in=[self.user]).exists():
return True
if obj.user.admin_of_organizations.filter(admins__in=[self.user]).count():
#if obj.user.admin_of_organizations.filter(active=True, admins__in=[self.user]).exists(): TODO: replace line below
if obj.user.admin_of_organizations.filter(admins__in=[self.user]).exists():
return True
if obj.team:
#if self.user in obj.team.organization.admins.filter(active=True): TODO: replace line below
if self.user in obj.team.organization.admins.all():
return True
return False
@ -585,6 +596,7 @@ class TeamAccess(BaseAccess):
if self.user.is_superuser:
return qs
return qs.filter(
#Q(organization__admins__in=[self.user], organization__active=True) | TODO: replace line below
Q(organization__admins__in=[self.user]) |
Q(users__in=[self.user])
)
@ -639,17 +651,21 @@ class ProjectAccess(BaseAccess):
allowed = [PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK]
return qs.filter(
Q(created_by=self.user) |
#Q(organizations__admins__in=[self.user], organizations__active=True) | TODO: replace 3 lines below
#Q(organizations__users__in=[self.user], organizations__active=True) |
#Q(teams__users__in=[self.user], teams__active=True) |
Q(organizations__admins__in=[self.user]) |
Q(organizations__users__in=[self.user]) |
Q(teams__users__in=[self.user]) |
Q(permissions__user=self.user, permissions__permission_type__in=allowed, permissions__active=True) |
Q(permissions__user=self.user, permissions__permission_type__in=allowed, permissions__active=True) | # TODO: add tests for these 2 lines?
Q(permissions__team__users__in=[self.user], permissions__permission_type__in=allowed, permissions__active=True)
)
def can_add(self, data):
if self.user.is_superuser:
return True
if self.user.admin_of_organizations.count():
#if self.user.admin_of_organizations.filter(active=True).exists(): TODO: replace line below
if self.user.admin_of_organizations.all().exists():
return True
return False
@ -658,7 +674,8 @@ class ProjectAccess(BaseAccess):
return True
if obj.created_by == self.user:
return True
if obj.organizations.filter(admins__in=[self.user]).count():
#if obj.organizations.filter(active=True, admins__in=[self.user]).exists(): TODO: replace line below
if obj.organizations.filter(admins__in=[self.user]).exists():
return True
return False
@ -707,12 +724,15 @@ class PermissionAccess(BaseAccess):
'project')
if self.user.is_superuser:
return qs
#orgs_as_admin = self.user.admin_of_organizations.filter(active=True) TODO: replace line below
orgs_as_admin = self.user.admin_of_organizations.all()
return qs.filter(
Q(user__organizations__in=orgs_as_admin) |
Q(user__admin_of_organizations__in=orgs_as_admin) |
#Q(team__organization__in=orgs_as_admin, team__active=True) | TODO: replace line below
Q(team__organization__in=orgs_as_admin) |
Q(user=self.user) |
#Q(team__users__in=[self.user], team__active=True) TODO: replace line below
Q(team__users__in=[self.user])
)
@ -802,7 +822,9 @@ class JobTemplateAccess(BaseAccess):
credential_qs = self.user.get_queryset(Credential)
base_qs = qs.filter(
Q(credential__in=credential_qs) | Q(credential__isnull=True),
Q(cloud_credential__in=credential_qs) | Q(cloud_credential__isnull=True),
)
# FIXME: Check active status on related objects!
org_admin_qs = base_qs.filter(
project__organizations__admins__in=[self.user]
)
@ -821,13 +843,17 @@ class JobTemplateAccess(BaseAccess):
def can_read(self, obj):
# you can only see the job templates that you have permission to launch.
data = dict(
inventory = obj.inventory.pk,
project = obj.project.pk,
job_type = obj.job_type,
)
data = {
'job_type': obj.job_type,
}
if obj.inventory and obj.inventory.pk:
data['inventory'] = obj.inventory.pk
if obj.project and obj.project.pk:
data['project'] = obj.project.pk
if obj.credential:
data['credential'] = obj.credential.pk
if obj.cloud_credential:
data['cloud_credential'] = obj.cloud_credential.pk
return self.can_add(data)
def can_add(self, data):
@ -850,6 +876,14 @@ class JobTemplateAccess(BaseAccess):
if not self.user.can_access(Credential, 'read', credential):
return False
# If a cloud credential is provided, the user should have read access.
cloud_credential_pk = get_pk_from_dict(data, 'cloud_credential')
if cloud_credential_pk:
cloud_credential = get_object_or_400(Credential,
pk=cloud_credential_pk)
if not self.user.can_access(Credential, 'read', cloud_credential):
return False
# Check that the given inventory ID is valid.
inventory_pk = get_pk_from_dict(data, 'inventory')
inventory = get_object_or_400(Inventory, pk=inventory_pk)
@ -1004,7 +1038,7 @@ class JobEventAccess(BaseAccess):
'host', 'parent')
qs = qs.prefetch_related('hosts', 'children')
# Filter certain "internal" events generating by async polling.
# Filter certain "internal" events generated by async polling.
qs = qs.exclude(event__in=('runner_on_ok', 'runner_on_failed'),
event_data__icontains='"ansible_job_id": "',
event_data__contains='"module_name": "async_status"')

View File

@ -45,6 +45,10 @@ class UsersTest(BaseTest):
self.post(url, expect=400, data=new_user, auth=self.get_super_credentials())
self.post(url, expect=201, data=new_user2, auth=self.get_normal_credentials())
self.post(url, expect=400, data=new_user2, auth=self.get_normal_credentials())
# Normal user cannot add users after his org is marked inactive.
self.organizations[0].mark_inactive()
new_user3 = dict(username='blippy3')
self.post(url, expect=403, data=new_user3, auth=self.get_normal_credentials())
def test_auth_token_login(self):
auth_token_url = reverse('api:auth_token_view')
@ -230,8 +234,15 @@ class UsersTest(BaseTest):
# Normal user is an org admin, can see all users.
data2 = self.get(url, expect=200, auth=self.get_normal_credentials())
self.assertEquals(data2['count'], 4)
# Other use can only see users in his org.
data1 = self.get(url, expect=200, auth=self.get_other_credentials())
self.assertEquals(data1['count'], 2)
# Normal user can no longer see all users after the organization he
# admins is marked inactive, nor can he see any other users that were
# in that org, so he only sees himself.
self.organizations[0].mark_inactive()
data3 = self.get(url, expect=200, auth=self.get_normal_credentials())
self.assertEquals(data3['count'], 1)
def test_super_user_can_delete_a_user_but_only_marked_inactive(self):
user_pk = self.normal_django_user.pk