Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api-docs/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def cli(output_path):
projects = []
for project_name in 'Pump Station', 'Prime Mover':
report('project', 'Creating project "%s"' % project_name)
project = utils.create_project(project_name, team=team, org=org)
project = utils.create_project(project_name, teams=[team], org=org)
release = utils.create_release(project=project, user=user)
report('event', 'Creating event for "%s"' % project_name)

Expand Down
9 changes: 7 additions & 2 deletions src/sentry/api/bases/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from sentry.auth import access
from sentry.auth.superuser import is_active_superuser
from sentry.models import (
ApiKey, Organization, OrganizationMemberTeam, Project, ReleaseProject, Team
ApiKey, Organization, OrganizationMemberTeam, Project, ProjectTeam, ReleaseProject, Team
)
from sentry.utils import auth

Expand Down Expand Up @@ -157,7 +157,12 @@ def get_allowed_projects(self, request, organization):
).values_list(
'team_id', flat=True
)
return Project.objects.filter(team_id__in=allowed_teams)

return Project.objects.filter(
id__in=ProjectTeam.objects.filter(
team_id__in=allowed_teams,
).values_list('project_id', flat=True)
)

def has_release_permission(self, request, organization, release):
return ReleaseProject.objects.filter(
Expand Down
4 changes: 2 additions & 2 deletions src/sentry/api/bases/organizationissues.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ def get(self, request, organization, member):

project_list = Project.objects.filter(
organization=organization,
team__in=OrganizationMemberTeam.objects.filter(
teams__in=OrganizationMemberTeam.objects.filter(
organizationmember=member,
).values('team')
).values('team'),
)

queryset = self.get_queryset(request, organization, member, project_list)
Expand Down
19 changes: 13 additions & 6 deletions src/sentry/api/bases/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
from sentry.app import raven
from sentry.models import Project, ProjectStatus

from .team import TeamPermission
from .organization import OrganizationPermission
from .team import has_team_permission


class ProjectPermission(TeamPermission):
class ProjectPermission(OrganizationPermission):
scope_map = {
'GET': ['project:read', 'project:write', 'project:admin'],
'POST': ['project:write', 'project:admin'],
Expand All @@ -17,7 +18,15 @@ class ProjectPermission(TeamPermission):
}

def has_object_permission(self, request, view, project):
return super(ProjectPermission, self).has_object_permission(request, view, project.team)
result = super(ProjectPermission,
self).has_object_permission(request, view, project.organization)

if not result:
return result

return any(
has_team_permission(request, team, self.scope_map) for team in project.teams.all()
)


class StrictProjectPermission(ProjectPermission):
Expand Down Expand Up @@ -74,15 +83,13 @@ def convert_args(self, request, organization_slug, project_slug, *args, **kwargs
project = Project.objects.filter(
organization__slug=organization_slug,
slug=project_slug,
).select_related('organization', 'team').get()
).select_related('organization').prefetch_related('teams').get()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL what prefetch_related does

except Project.DoesNotExist:
raise ResourceDoesNotExist

if project.status != ProjectStatus.VISIBLE:
raise ResourceDoesNotExist

project.team.organization = project.organization

self.check_object_permissions(request, project)

raven.tags_context({
Expand Down
14 changes: 9 additions & 5 deletions src/sentry/api/bases/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
from .organization import OrganizationPermission


def has_team_permission(request, team, scope_map):
if not (request.user and request.user.is_authenticated()) and request.auth:
return request.auth.organization_id == team.organization.id

allowed_scopes = set(scope_map.get(request.method, []))
return any(request.access.has_team_scope(team, s) for s in allowed_scopes)


class TeamPermission(OrganizationPermission):
scope_map = {
'GET': ['team:read', 'team:write', 'team:admin'],
Expand All @@ -22,11 +30,7 @@ def has_object_permission(self, request, view, team):
if not result:
return result

if not (request.user and request.user.is_authenticated()) and request.auth:
return request.auth.organization_id == team.organization.id

allowed_scopes = set(self.scope_map.get(request.method, []))
return any(request.access.has_team_scope(team, s) for s in allowed_scopes)
return has_team_permission(request, team, self.scope_map)


class TeamEndpoint(Endpoint):
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/organization_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def get(self, request, organization, member):
queryset = Activity.objects.filter(
project__in=Project.objects.filter(
organization=organization,
team__in=OrganizationMemberTeam.objects.filter(
teams__in=OrganizationMemberTeam.objects.filter(
organizationmember=member,
).values('team')
)
Expand Down
12 changes: 6 additions & 6 deletions src/sentry/api/endpoints/organization_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,18 @@ def get(self, request, organization):
if request.auth and not request.user.is_authenticated():
# TODO: remove this, no longer supported probably
if hasattr(request.auth, 'project'):
team_list = [request.auth.project.team]
team_list = list(request.auth.project.teams.all())
queryset = queryset = Project.objects.filter(
id=request.auth.project.id,
).select_related('team')
).prefetch_related('teams')
elif request.auth.organization is not None:
org = request.auth.organization
team_list = list(Team.objects.filter(
organization=org,
))
queryset = Project.objects.filter(
team__in=team_list,
).select_related('team')
teams__in=team_list,
).prefetch_related('teams')
else:
return Response(
{
Expand All @@ -76,8 +76,8 @@ def get(self, request, organization):
else:
team_list = list(request.access.teams)
queryset = Project.objects.filter(
team__in=team_list,
).select_related('team')
teams__in=team_list,
).prefetch_related('team')

return self.paginate(
request=request,
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/organization_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def get(self, request, organization):
team=team,
user=request.user,
))
keys = [p.id for p in project_list]
keys = list({p.id for p in project_list})
else:
raise ValueError('Invalid group: %s' % group)

Expand Down
13 changes: 10 additions & 3 deletions src/sentry/api/endpoints/organization_user_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from sentry.api.bases.organization import OrganizationEndpoint
from sentry.api.serializers import serialize
from sentry.api.serializers.models.group import TagBasedStreamGroupSerializer
from sentry.models import (EventUser, Group, Project)
from sentry.models import (EventUser, Group, ProjectTeam, Team)


class OrganizationUserIssuesEndpoint(OrganizationEndpoint, EnvironmentMixin):
Expand All @@ -21,8 +21,15 @@ def get(self, request, organization, user_id):
)
# they have organization access but not to this project, thus
# they shouldn't be able to see this user
if not request.access.has_team_access(
Project.objects.select_related('team').get(pk=euser.project_id).team):
teams = Team.objects.filter(
organization=organization,
id__in=ProjectTeam.objects.filter(
project_id=euser.project_id,
).values_list('team_id', flat=True)
)
has_team_access = any([request.access.has_team_access(t) for t in teams])

if not has_team_access:
return Response([])

other_eusers = euser.find_similar_users(request.user)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def get(self, request, organization):
# limit to only teams user has opted into
project_ids = list(
Project.objects.filter(
team__in=OrganizationMemberTeam.objects.filter(
teams__in=OrganizationMemberTeam.objects.filter(
organizationmember__user=request.user,
organizationmember__organization=organization,
is_active=True,
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/project_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get(self, request):
queryset = queryset.none()
elif not is_active_superuser(request):
queryset = queryset.filter(
team__organizationmember__user=request.user,
teams__organizationmember__user=request.user,
)

query = request.GET.get('query')
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/project_member_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def get(self, request, project):
queryset = OrganizationMember.objects.filter(
Q(user__is_active=True) | Q(user__isnull=True),
organization=project.organization,
teams=project.team,
teams=project.teams.all(),
).select_related('user')

member_list = sorted(
Expand Down
5 changes: 4 additions & 1 deletion src/sentry/api/endpoints/project_search_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ def put(self, request, project, search_id):
except SavedSearch.DoesNotExist:
raise ResourceDoesNotExist

if request.access.has_team_scope(project.team, 'project:write'):
has_team_scope = any(
request.access.has_team_scope(team, 'project:write') for team in project.teams.all()
)
if has_team_scope:
serializer = SavedSearchSerializer(data=request.DATA, partial=True)
else:
serializer = LimitedSavedSearchSerializer(data=request.DATA, partial=True)
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/team_project_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def get(self, request, team):
else:
# TODO(dcramer): status should be selectable
results = list(Project.objects.filter(
team=team,
teams=team,
status=ProjectStatus.VISIBLE,
))

Expand Down
23 changes: 18 additions & 5 deletions src/sentry/api/serializers/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from sentry.constants import StatsPeriod
from sentry.digests import backend as digests
from sentry.models import (
Project, ProjectBookmark, ProjectOption, ProjectPlatform, ProjectStatus, Release, UserOption,
DEFAULT_SUBJECT_TEMPLATE
Project, ProjectBookmark, ProjectOption, ProjectPlatform, ProjectStatus, ProjectTeam,
Release, UserOption, DEFAULT_SUBJECT_TEMPLATE
)
from sentry.utils.data_filters import FilterTypes

Expand Down Expand Up @@ -159,16 +159,29 @@ def get_attrs(self, item_list, user):
attrs = super(ProjectWithTeamSerializer,
self).get_attrs(item_list, user)

project_teams = list(
ProjectTeam.objects.filter(
project__in=item_list,
).select_related('team')
)

teams = {d['id']: d for d in serialize(
list(set(i.team for i in item_list)), user)}
list(set(pt.team for pt in project_teams)), user)}

teams_by_project_id = defaultdict(list)
for pt in project_teams:
teams_by_project_id[pt.project_id].append(teams[six.text_type(pt.team_id)])

for item in item_list:
attrs[item]['team'] = teams[six.text_type(item.team_id)]
attrs[item]['teams'] = teams_by_project_id[item.id]
return attrs

def serialize(self, obj, attrs, user):
data = super(ProjectWithTeamSerializer,
self).serialize(obj, attrs, user)
data['team'] = attrs['team']
# TODO(jess): remove this when this is deprecated
data['team'] = attrs['teams'][0]
data['teams'] = attrs['teams']
return data


Expand Down
27 changes: 17 additions & 10 deletions src/sentry/api/serializers/models/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from sentry.api.serializers import Serializer, register, serialize
from sentry.auth.superuser import is_active_superuser
from sentry.models import (
OrganizationAccessRequest, OrganizationMemberTeam, Project, ProjectStatus, Team
OrganizationAccessRequest, OrganizationMemberTeam, ProjectStatus,
ProjectTeam, Team
)


Expand Down Expand Up @@ -70,24 +71,30 @@ def serialize(self, obj, attrs, user):

class TeamWithProjectsSerializer(TeamSerializer):
def get_attrs(self, item_list, user):
project_qs = list(
Project.objects.filter(
project_teams = list(
ProjectTeam.objects.filter(
team__in=item_list,
status=ProjectStatus.VISIBLE,
).order_by('name', 'slug')
project__status=ProjectStatus.VISIBLE,
).order_by('project__name', 'project__slug').select_related('project')
)

team_map = {i.id: i for i in item_list}
# TODO(dcramer): we should query in bulk for ones we're missing here
orgs = {i.organization_id: i.organization for i in item_list}

for project in project_qs:
project._team_cache = team_map[project.team_id]
project._organization_cache = orgs[project.organization_id]
for project_team in project_teams:
# TODO(jess): remove when we've completely deprecated Project.team
project_team.project._team_cache = team_map[project_team.project.team_id]
project_team.project._organization_cache = orgs[project_team.project.organization_id]

projects = [pt.project for pt in project_teams]
projects_by_id = {
project.id: data for project, data in zip(projects, serialize(projects, user))
}

project_map = defaultdict(list)
for project, data in zip(project_qs, serialize(project_qs, user)):
project_map[project.team_id].append(data)
for project_team in project_teams:
project_map[project_team.team_id].append(projects_by_id[project_team.project_id])

result = super(TeamWithProjectsSerializer, self).get_attrs(item_list, user)
for team in item_list:
Expand Down
4 changes: 2 additions & 2 deletions src/sentry/integrations/cloudflare/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def project_from_json(self, request, data, scope='project:write'):

projects = Project.objects.filter(
organization=org,
team__in=Team.objects.get_for_user(org, request.user, scope='project:write'),
teams__in=Team.objects.get_for_user(org, request.user, scope='project:write'),
)
for project in projects:
if six.text_type(project.id) == project_id:
Expand Down Expand Up @@ -144,7 +144,7 @@ def on_organization_change(self, request, data, is_test):

projects = sorted(Project.objects.filter(
organization=org,
team__in=Team.objects.get_for_user(org, request.user, scope='project:write'),
teams__in=Team.objects.get_for_user(org, request.user, scope='project:write'),
), key=lambda x: x.slug)

enum_choices = [six.text_type(o.id) for o in projects]
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/management/commands/create_sample_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def handle(self, **options):
project = Project.objects.get(id=options['project'])
elif '/' in options['project']:
t_slug, p_slug = options['project'].split('/', 1)
project = Project.objects.get(slug=p_slug, team__slug=t_slug)
project = Project.objects.get(slug=p_slug, teams__slug=t_slug)
else:
raise CommandError(
'Project must be specified as team-slug/project-slug or a project id'
Expand Down
4 changes: 0 additions & 4 deletions src/sentry/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,6 @@ def message_short(self):
warnings.warn('Event.message_short is deprecated, use Event.title', DeprecationWarning)
return self.title

@property
def team(self):
return self.project.team

@property
def organization(self):
return self.project.organization
Expand Down
4 changes: 0 additions & 4 deletions src/sentry/models/eventmapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ class Meta:

__repr__ = sane_repr('project_id', 'group_id', 'event_id')

@property
def team(self):
return self.project.team

# Implement a ForeignKey-like accessor for backwards compat
def _set_group(self, group):
self.group_id = group.id
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/eventuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def find_similar_users(self, user):
# limit to only teams user has opted into
project_ids = list(
Project.objects.filter(
team__in=OrganizationMemberTeam.objects.filter(
teams__in=OrganizationMemberTeam.objects.filter(
organizationmember__user=user,
organizationmember__organization__project=self.project_id,
is_active=True,
Expand Down
Loading