Browse Source

feat(api):restrict member invite (#6418)

* restrict-member-invite based on flag, defaults to true
Max Bittker 7 years ago
parent
commit
8f09f4b3e6

+ 5 - 1
src/sentry/api/endpoints/organization_member_index.py

@@ -8,7 +8,7 @@ from rest_framework.response import Response
 from django.conf import settings
 from django.conf import settings
 
 
 from sentry.app import locks
 from sentry.app import locks
-from sentry import roles
+from sentry import roles, features
 from sentry.api.bases.organization import (
 from sentry.api.bases.organization import (
     OrganizationEndpoint, OrganizationPermission)
     OrganizationEndpoint, OrganizationPermission)
 from sentry.api.paginator import OffsetPaginator
 from sentry.api.paginator import OffsetPaginator
@@ -92,6 +92,10 @@ class OrganizationMemberIndexEndpoint(OrganizationEndpoint):
         # TODO: If the member already exists, should this still update the role and team?
         # TODO: If the member already exists, should this still update the role and team?
         # For now, it doesn't, but simply returns the existing object
         # For now, it doesn't, but simply returns the existing object
 
 
+        if not features.has('organizations:invite-members', organization, actor=request.user):
+            return Response(
+                {'organization': 'Your organization is not allowed to invite members'}, status=401)
+
         serializer = OrganizationMemberSerializer(data=request.DATA)
         serializer = OrganizationMemberSerializer(data=request.DATA)
 
 
         if not serializer.is_valid():
         if not serializer.is_valid():

+ 1 - 0
src/sentry/conf/server.py

@@ -726,6 +726,7 @@ SENTRY_FEATURES = {
     'organizations:sso-rippling': False,
     'organizations:sso-rippling': False,
     'organizations:group-unmerge': False,
     'organizations:group-unmerge': False,
     'organizations:integrations-v3': False,
     'organizations:integrations-v3': False,
+    'organizations:invite-members': True,
     'projects:global-events': False,
     'projects:global-events': False,
     'projects:plugins': True,
     'projects:plugins': True,
     'projects:dsym': False,
     'projects:dsym': False,

+ 1 - 0
src/sentry/features/__init__.py

@@ -15,6 +15,7 @@ default_manager.add('organizations:onboarding', OrganizationFeature)  # NOQA
 default_manager.add('organizations:repos', OrganizationFeature)  # NOQA
 default_manager.add('organizations:repos', OrganizationFeature)  # NOQA
 default_manager.add('organizations:release-commits', OrganizationFeature)  # NOQA
 default_manager.add('organizations:release-commits', OrganizationFeature)  # NOQA
 default_manager.add('organizations:group-unmerge', OrganizationFeature)  # NOQA
 default_manager.add('organizations:group-unmerge', OrganizationFeature)  # NOQA
+default_manager.add('organizations:invite-members', OrganizationFeature)  # NOQA
 default_manager.add('organizations:integrations-v3', OrganizationFeature)  # NOQA
 default_manager.add('organizations:integrations-v3', OrganizationFeature)  # NOQA
 default_manager.add('projects:similarity-view', ProjectFeature)  # NOQA
 default_manager.add('projects:similarity-view', ProjectFeature)  # NOQA
 default_manager.add('projects:global-events', ProjectFeature)  # NOQA
 default_manager.add('projects:global-events', ProjectFeature)  # NOQA

+ 7 - 1
src/sentry/static/sentry/app/views/inviteMember/inviteMember.jsx

@@ -83,7 +83,13 @@ const InviteMember = React.createClass({
           resolve();
           resolve();
         },
         },
         error: err => {
         error: err => {
-          if (err.status === 409) {
+          if (err.status === 401) {
+            AlertActions.addAlert({
+              message: "You aren't allowed to invite members.",
+              type: 'error'
+            });
+            reject(err.responseJSON);
+          } else if (err.status === 409) {
             AlertActions.addAlert({
             AlertActions.addAlert({
               message: `User already exists: ${email}`,
               message: `User already exists: ${email}`,
               type: 'info'
               type: 'info'

+ 30 - 3
tests/sentry/api/endpoints/test_organization_member_index.py

@@ -5,6 +5,7 @@ from django.core import mail
 
 
 from sentry.testutils import APITestCase
 from sentry.testutils import APITestCase
 from sentry.models import OrganizationMember, OrganizationMemberTeam
 from sentry.models import OrganizationMember, OrganizationMemberTeam
+from sentry.testutils.helpers import Feature
 
 
 
 
 class OrganizationMemberListTest(APITestCase):
 class OrganizationMemberListTest(APITestCase):
@@ -237,8 +238,34 @@ class OrganizationMemberListTest(APITestCase):
 
 
         assert response.status_code == 403
         assert response.status_code == 403
 
 
-    def test_duplicate_email_invites(self):
-        pass
+    def test_respects_feature_flag(self):
+        self.login_as(user=self.owner_user)
+
+        user = self.create_user('baz@example.com')
+
+        with Feature({'organizations:invite-members': False}):
+            resp = self.client.post(
+                self.url, {'email': user.email, 'role': 'member', 'teams': [
+                    self.team.slug,
+                ]})
+
+        assert resp.status_code == 401
 
 
     def test_no_team_invites(self):
     def test_no_team_invites(self):
-        pass
+        self.login_as(user=self.owner_user)
+        response = self.client.post(
+            self.url, {
+                'email': 'eric@localhost', 'role': 'owner', 'teams': []
+            })
+
+        assert response.status_code == 201
+        assert response.data['email'] == 'eric@localhost'
+
+    def test_invalid_team_invites(self):
+        self.login_as(user=self.owner_user)
+        response = self.client.post(
+            self.url, {
+                'email': 'eric@localhost', 'role': 'owner', 'teams': ['faketeam']
+            })
+
+        assert response.status_code == 400