We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Slack Integration: 
Hi, I'm having trouble setting up the new Slack integration to work with private channels.
- With the legacy integration we had Sentry sending messages into private channels.
- When authorizing the Slack app I selected "Specific channels" and entered the names of the private channels, as well as "slackbot, which is private to you"..
- The authorization appeared to succeed.  After authorizing I saw a message `Sentry APP [2:39 PM]
was added to this conversation by Luke Moore.` in those channels.
- When editing the alert rule I've set the "take these actions" to send a notification to the private workspace but I see the error: 'The slack resource "website-private" does not exist or has not been granted access in the SideFX Slack workspace.'  I've tried with and without a leading "#".

Am I doing something wrong?
![image](https://user-images.githubusercontent.com/6863550/38211023-2492766c-3687-11e8-9a6f-2715dd542580.png)
![image](https://user-images.githubusercontent.com/6863550/38210995-0cdbf4da-3687-11e8-9504-6c66a44feb04.png)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/sentry/integrations/slack/integration.py`
Content:
```
1 from __future__ import absolute_import
2 
3 from sentry import http
4 from sentry.integrations import Integration, IntegrationMetadata
5 from sentry.utils.pipeline import NestedPipelineView
6 from sentry.identity.pipeline import IdentityProviderPipeline
7 from sentry.utils.http import absolute_uri
8 
9 DESCRIPTION = """
10 Define a relationship between Sentry and your Slack workspace(s).
11 
12  * Unfurls Sentry URLs in slack, providing context and actionability on issues
13    directly within your Slack workspace.
14  * Resolve, ignore, and assign issues with minimal context switching.
15  * Configure rule based Slack notifications to automatically be posted into the
16    specified channel.
17 """
18 
19 alert_link = {
20     'text': 'Looking to send Sentry alerts to Slack? Add an **Alert Rule** for this project.',
21     'link': '/settings/{orgId}/{projectId}/alerts/rules/'
22 }
23 
24 metadata = IntegrationMetadata(
25     description=DESCRIPTION.strip(),
26     author='The Sentry Team',
27     issue_url='https://github.com/getsentry/sentry/issues/new?title=Slack%20Integration:%20&labels=Component%3A%20Integrations',
28     source_url='https://github.com/getsentry/sentry/tree/master/src/sentry/integrations/slack',
29     aspects={
30         'alert_link': alert_link,
31     }
32 )
33 
34 
35 class SlackIntegration(Integration):
36     key = 'slack'
37     name = 'Slack'
38     metadata = metadata
39 
40     identity_oauth_scopes = frozenset([
41         'channels:read',
42         'users:read'
43         'chat:write',
44         'links:read',
45         'links:write',
46         'team:read',
47     ])
48 
49     setup_dialog_config = {
50         'width': 600,
51         'height': 900,
52     }
53 
54     def get_pipeline_views(self):
55         identity_pipeline_config = {
56             'oauth_scopes': self.identity_oauth_scopes,
57             'redirect_url': absolute_uri('/extensions/slack/setup/'),
58         }
59 
60         identity_pipeline_view = NestedPipelineView(
61             bind_key='identity',
62             provider_key='slack',
63             pipeline_cls=IdentityProviderPipeline,
64             config=identity_pipeline_config,
65         )
66 
67         return [identity_pipeline_view]
68 
69     def get_team_info(self, access_token):
70         payload = {
71             'token': access_token,
72         }
73 
74         session = http.build_session()
75         resp = session.get('https://slack.com/api/team.info', params=payload)
76         resp.raise_for_status()
77         resp = resp.json()
78 
79         return resp['team']
80 
81     def build_integration(self, state):
82         data = state['identity']['data']
83         assert data['ok']
84 
85         scopes = sorted(self.identity_oauth_scopes)
86         team_data = self.get_team_info(data['access_token'])
87 
88         return {
89             'name': data['team_name'],
90             'external_id': data['team_id'],
91             'metadata': {
92                 'access_token': data['access_token'],
93                 'scopes': scopes,
94                 'icon': team_data['icon']['image_132'],
95                 'domain_name': team_data['domain'] + '.slack.com',
96             },
97             'user_identity': {
98                 'type': 'slack',
99                 'external_id': data['installer_user_id'],
100                 'scopes': [],
101                 'data': {},
102             },
103         }
104
```

Path: `src/sentry/integrations/slack/notify_action.py`
Content:
```
1 from __future__ import absolute_import
2 
3 from django import forms
4 from django.utils.translation import ugettext_lazy as _
5 
6 from sentry import http
7 from sentry.rules.actions.base import EventAction
8 from sentry.utils import metrics, json
9 from sentry.models import Integration
10 
11 from .utils import build_attachment
12 
13 MEMBER_PREFIX = '@'
14 CHANNEL_PREFIX = '#'
15 strip_channel_chars = ''.join([MEMBER_PREFIX, CHANNEL_PREFIX])
16 
17 
18 class SlackNotifyServiceForm(forms.Form):
19     workspace = forms.ChoiceField(choices=(), widget=forms.Select(
20     ))
21     channel = forms.CharField(widget=forms.TextInput())
22     channel_id = forms.HiddenInput()
23     tags = forms.CharField(required=False, widget=forms.TextInput())
24 
25     def __init__(self, *args, **kwargs):
26         # NOTE: Workspace maps directly to the integration ID
27         workspace_list = [(i.id, i.name) for i in kwargs.pop('integrations')]
28         self.channel_transformer = kwargs.pop('channel_transformer')
29 
30         super(SlackNotifyServiceForm, self).__init__(*args, **kwargs)
31 
32         if workspace_list:
33             self.fields['workspace'].initial = workspace_list[0][0]
34 
35         self.fields['workspace'].choices = workspace_list
36         self.fields['workspace'].widget.choices = self.fields['workspace'].choices
37 
38     def clean(self):
39         cleaned_data = super(SlackNotifyServiceForm, self).clean()
40 
41         workspace = cleaned_data.get('workspace')
42         channel = cleaned_data.get('channel', '').lstrip(strip_channel_chars)
43 
44         channel_id = self.channel_transformer(workspace, channel)
45 
46         if channel_id is None and workspace is not None:
47             params = {
48                 'channel': channel,
49                 'workspace': dict(self.fields['workspace'].choices).get(int(workspace)),
50             }
51 
52             raise forms.ValidationError(
53                 _('The slack resource "%(channel)s" does not exist or has not been granted access in the %(workspace)s Slack workspace.'),
54                 code='invalid',
55                 params=params,
56             )
57 
58         channel_prefix, channel_id = channel_id
59         cleaned_data['channel'] = channel_prefix + channel
60         cleaned_data['channel_id'] = channel_id
61 
62         return cleaned_data
63 
64 
65 class SlackNotifyServiceAction(EventAction):
66     form_cls = SlackNotifyServiceForm
67     label = u'Send a notification to the {workspace} Slack workspace to {channel} and include tags {tags}'
68 
69     def __init__(self, *args, **kwargs):
70         super(SlackNotifyServiceAction, self).__init__(*args, **kwargs)
71         self.form_fields = {
72             'workspace': {
73                 'type': 'choice',
74                 'choices': [(i.id, i.name) for i in self.get_integrations()]
75             },
76             'channel': {
77                 'type': 'string',
78                 'placeholder': 'i.e #critical'
79             },
80             'tags': {
81                 'type': 'string',
82                 'placeholder': 'i.e environment,user,my_tag'
83             }
84         }
85 
86     def is_enabled(self):
87         return self.get_integrations().exists()
88 
89     def after(self, event, state):
90         if event.group.is_ignored():
91             return
92 
93         integration_id = self.get_option('workspace')
94         channel = self.get_option('channel_id')
95         tags = set(self.get_tags_list())
96 
97         try:
98             integration = Integration.objects.get(
99                 provider='slack',
100                 organizations=self.project.organization,
101                 id=integration_id
102             )
103         except Integration.DoesNotExist:
104             # Integration removed, rule still active.
105             return
106 
107         def send_notification(event, futures):
108             rules = [f.rule for f in futures]
109             attachment = build_attachment(event.group, event=event, tags=tags, rules=rules)
110 
111             payload = {
112                 'token': integration.metadata['access_token'],
113                 'channel': channel,
114                 'attachments': json.dumps([attachment]),
115             }
116 
117             session = http.build_session()
118             resp = session.post('https://slack.com/api/chat.postMessage', data=payload)
119             resp.raise_for_status()
120             resp = resp.json()
121             if not resp.get('ok'):
122                 self.logger.info('rule.fail.slack_post', extra={'error': resp.get('error')})
123 
124         key = u'slack:{}:{}'.format(integration_id, channel)
125 
126         metrics.incr('notifications.sent', instance='slack.notification')
127         yield self.future(send_notification, key=key)
128 
129     def render_label(self):
130         try:
131             integration_name = Integration.objects.get(
132                 provider='slack',
133                 organizations=self.project.organization,
134                 id=self.get_option('workspace')
135             ).name
136         except Integration.DoesNotExist:
137             integration_name = '[removed]'
138 
139         tags = self.get_tags_list()
140 
141         return self.label.format(
142             workspace=integration_name,
143             channel=self.get_option('channel'),
144             tags=u'[{}]'.format(', '.join(tags)),
145         )
146 
147     def get_tags_list(self):
148         return [s.strip() for s in self.get_option('tags', '').split(',')]
149 
150     def get_integrations(self):
151         return Integration.objects.filter(
152             provider='slack',
153             organizations=self.project.organization,
154         )
155 
156     def get_form_instance(self):
157         return self.form_cls(
158             self.data,
159             integrations=self.get_integrations(),
160             channel_transformer=self.get_channel_id,
161         )
162 
163     def get_channel_id(self, integration_id, name):
164         try:
165             integration = Integration.objects.get(
166                 provider='slack',
167                 organizations=self.project.organization,
168                 id=integration_id,
169             )
170         except Integration.DoesNotExist:
171             return None
172 
173         session = http.build_session()
174 
175         token_payload = {
176             'token': integration.metadata['access_token'],
177         }
178 
179         # Get slack app resource permissions
180         resp = session.get('https://slack.com/api/apps.permissions.info', params=token_payload)
181         resp = resp.json()
182         if not resp.get('ok'):
183             extra = {'error': resp.get('error')}
184             self.logger.info('rule.slack.permission_check_failed', extra=extra)
185             return None
186 
187         channel_perms = resp['info']['channel']['resources']
188         dm_perms = resp['info']['im']['resources']
189 
190         # Look for channel ID
191         channels_payload = dict(token_payload, **{
192             'exclude_archived': False,
193             'exclude_members': True,
194         })
195 
196         resp = session.get('https://slack.com/api/channels.list', params=channels_payload)
197         resp = resp.json()
198         if not resp.get('ok'):
199             self.logger.info('rule.slack.channel_list_failed', extra={'error': resp.get('error')})
200             return None
201 
202         channel_id = {c['name']: c['id'] for c in resp['channels']}.get(name)
203 
204         if channel_id:
205             if channel_id in channel_perms['excluded_ids']:
206                 return None
207 
208             if not channel_perms['wildcard'] and channel_id not in channel_perms['ids']:
209                 return None
210 
211             return (CHANNEL_PREFIX, channel_id)
212 
213         # Look for user ID
214         resp = session.get('https://slack.com/api/users.list', params=token_payload)
215         resp = resp.json()
216         if not resp.get('ok'):
217             self.logger.info('rule.slack.user_list_failed', extra={'error': resp.get('error')})
218             return None
219 
220         member_id = {c['name']: c['id'] for c in resp['members']}.get(name)
221 
222         if member_id and member_id in dm_perms['ids']:
223             return (MEMBER_PREFIX, member_id)
224 
225         return None
226
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/sentry/integrations/slack/integration.py b/src/sentry/integrations/slack/integration.py
--- a/src/sentry/integrations/slack/integration.py
+++ b/src/sentry/integrations/slack/integration.py
@@ -39,7 +39,8 @@
 
     identity_oauth_scopes = frozenset([
         'channels:read',
-        'users:read'
+        'groups:read',
+        'users:read',
         'chat:write',
         'links:read',
         'links:write',
diff --git a/src/sentry/integrations/slack/notify_action.py b/src/sentry/integrations/slack/notify_action.py
--- a/src/sentry/integrations/slack/notify_action.py
+++ b/src/sentry/integrations/slack/notify_action.py
@@ -201,6 +201,17 @@
 
         channel_id = {c['name']: c['id'] for c in resp['channels']}.get(name)
 
+        # Channel may be private, use the groups.list endpoint to find them
+        if not channel_id:
+            resp = session.get('https://slack.com/api/groups.list', params=channels_payload)
+            resp = resp.json()
+            if not resp.get('ok'):
+                self.logger.info('rule.slack.group_list_failed', extra={'error': resp.get('error')})
+                return None
+
+            channel_id = {c['name']: c['id'] for c in resp['groups']}.get(name)
+
+        # We should sufficiently have been able to find the channel by now
         if channel_id:
             if channel_id in channel_perms['excluded_ids']:
                 return None

{"golden_diff": "diff --git a/src/sentry/integrations/slack/integration.py b/src/sentry/integrations/slack/integration.py\n--- a/src/sentry/integrations/slack/integration.py\n+++ b/src/sentry/integrations/slack/integration.py\n@@ -39,7 +39,8 @@\n \n     identity_oauth_scopes = frozenset([\n         'channels:read',\n-        'users:read'\n+        'groups:read',\n+        'users:read',\n         'chat:write',\n         'links:read',\n         'links:write',\ndiff --git a/src/sentry/integrations/slack/notify_action.py b/src/sentry/integrations/slack/notify_action.py\n--- a/src/sentry/integrations/slack/notify_action.py\n+++ b/src/sentry/integrations/slack/notify_action.py\n@@ -201,6 +201,17 @@\n \n         channel_id = {c['name']: c['id'] for c in resp['channels']}.get(name)\n \n+        # Channel may be private, use the groups.list endpoint to find them\n+        if not channel_id:\n+            resp = session.get('https://slack.com/api/groups.list', params=channels_payload)\n+            resp = resp.json()\n+            if not resp.get('ok'):\n+                self.logger.info('rule.slack.group_list_failed', extra={'error': resp.get('error')})\n+                return None\n+\n+            channel_id = {c['name']: c['id'] for c in resp['groups']}.get(name)\n+\n+        # We should sufficiently have been able to find the channel by now\n         if channel_id:\n             if channel_id in channel_perms['excluded_ids']:\n                 return None\n", "issue": "Slack Integration: \nHi, I'm having trouble setting up the new Slack integration to work with private channels.\r\n- With the legacy integration we had Sentry sending messages into private channels.\r\n- When authorizing the Slack app I selected \"Specific channels\" and entered the names of the private channels, as well as \"slackbot, which is private to you\"..\r\n- The authorization appeared to succeed.  After authorizing I saw a message `Sentry APP [2:39 PM]\r\nwas added to this conversation by Luke Moore.` in those channels.\r\n- When editing the alert rule I've set the \"take these actions\" to send a notification to the private workspace but I see the error: 'The slack resource \"website-private\" does not exist or has not been granted access in the SideFX Slack workspace.'  I've tried with and without a leading \"#\".\r\n\r\nAm I doing something wrong?\r\n![image](https://user-images.githubusercontent.com/6863550/38211023-2492766c-3687-11e8-9a6f-2715dd542580.png)\r\n![image](https://user-images.githubusercontent.com/6863550/38210995-0cdbf4da-3687-11e8-9504-6c66a44feb04.png)\n", "before_files": [{"content": "from __future__ import absolute_import\n\nfrom sentry import http\nfrom sentry.integrations import Integration, IntegrationMetadata\nfrom sentry.utils.pipeline import NestedPipelineView\nfrom sentry.identity.pipeline import IdentityProviderPipeline\nfrom sentry.utils.http import absolute_uri\n\nDESCRIPTION = \"\"\"\nDefine a relationship between Sentry and your Slack workspace(s).\n\n * Unfurls Sentry URLs in slack, providing context and actionability on issues\n   directly within your Slack workspace.\n * Resolve, ignore, and assign issues with minimal context switching.\n * Configure rule based Slack notifications to automatically be posted into the\n   specified channel.\n\"\"\"\n\nalert_link = {\n    'text': 'Looking to send Sentry alerts to Slack? Add an **Alert Rule** for this project.',\n    'link': '/settings/{orgId}/{projectId}/alerts/rules/'\n}\n\nmetadata = IntegrationMetadata(\n    description=DESCRIPTION.strip(),\n    author='The Sentry Team',\n    issue_url='https://github.com/getsentry/sentry/issues/new?title=Slack%20Integration:%20&labels=Component%3A%20Integrations',\n    source_url='https://github.com/getsentry/sentry/tree/master/src/sentry/integrations/slack',\n    aspects={\n        'alert_link': alert_link,\n    }\n)\n\n\nclass SlackIntegration(Integration):\n    key = 'slack'\n    name = 'Slack'\n    metadata = metadata\n\n    identity_oauth_scopes = frozenset([\n        'channels:read',\n        'users:read'\n        'chat:write',\n        'links:read',\n        'links:write',\n        'team:read',\n    ])\n\n    setup_dialog_config = {\n        'width': 600,\n        'height': 900,\n    }\n\n    def get_pipeline_views(self):\n        identity_pipeline_config = {\n            'oauth_scopes': self.identity_oauth_scopes,\n            'redirect_url': absolute_uri('/extensions/slack/setup/'),\n        }\n\n        identity_pipeline_view = NestedPipelineView(\n            bind_key='identity',\n            provider_key='slack',\n            pipeline_cls=IdentityProviderPipeline,\n            config=identity_pipeline_config,\n        )\n\n        return [identity_pipeline_view]\n\n    def get_team_info(self, access_token):\n        payload = {\n            'token': access_token,\n        }\n\n        session = http.build_session()\n        resp = session.get('https://slack.com/api/team.info', params=payload)\n        resp.raise_for_status()\n        resp = resp.json()\n\n        return resp['team']\n\n    def build_integration(self, state):\n        data = state['identity']['data']\n        assert data['ok']\n\n        scopes = sorted(self.identity_oauth_scopes)\n        team_data = self.get_team_info(data['access_token'])\n\n        return {\n            'name': data['team_name'],\n            'external_id': data['team_id'],\n            'metadata': {\n                'access_token': data['access_token'],\n                'scopes': scopes,\n                'icon': team_data['icon']['image_132'],\n                'domain_name': team_data['domain'] + '.slack.com',\n            },\n            'user_identity': {\n                'type': 'slack',\n                'external_id': data['installer_user_id'],\n                'scopes': [],\n                'data': {},\n            },\n        }\n", "path": "src/sentry/integrations/slack/integration.py"}, {"content": "from __future__ import absolute_import\n\nfrom django import forms\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom sentry import http\nfrom sentry.rules.actions.base import EventAction\nfrom sentry.utils import metrics, json\nfrom sentry.models import Integration\n\nfrom .utils import build_attachment\n\nMEMBER_PREFIX = '@'\nCHANNEL_PREFIX = '#'\nstrip_channel_chars = ''.join([MEMBER_PREFIX, CHANNEL_PREFIX])\n\n\nclass SlackNotifyServiceForm(forms.Form):\n    workspace = forms.ChoiceField(choices=(), widget=forms.Select(\n    ))\n    channel = forms.CharField(widget=forms.TextInput())\n    channel_id = forms.HiddenInput()\n    tags = forms.CharField(required=False, widget=forms.TextInput())\n\n    def __init__(self, *args, **kwargs):\n        # NOTE: Workspace maps directly to the integration ID\n        workspace_list = [(i.id, i.name) for i in kwargs.pop('integrations')]\n        self.channel_transformer = kwargs.pop('channel_transformer')\n\n        super(SlackNotifyServiceForm, self).__init__(*args, **kwargs)\n\n        if workspace_list:\n            self.fields['workspace'].initial = workspace_list[0][0]\n\n        self.fields['workspace'].choices = workspace_list\n        self.fields['workspace'].widget.choices = self.fields['workspace'].choices\n\n    def clean(self):\n        cleaned_data = super(SlackNotifyServiceForm, self).clean()\n\n        workspace = cleaned_data.get('workspace')\n        channel = cleaned_data.get('channel', '').lstrip(strip_channel_chars)\n\n        channel_id = self.channel_transformer(workspace, channel)\n\n        if channel_id is None and workspace is not None:\n            params = {\n                'channel': channel,\n                'workspace': dict(self.fields['workspace'].choices).get(int(workspace)),\n            }\n\n            raise forms.ValidationError(\n                _('The slack resource \"%(channel)s\" does not exist or has not been granted access in the %(workspace)s Slack workspace.'),\n                code='invalid',\n                params=params,\n            )\n\n        channel_prefix, channel_id = channel_id\n        cleaned_data['channel'] = channel_prefix + channel\n        cleaned_data['channel_id'] = channel_id\n\n        return cleaned_data\n\n\nclass SlackNotifyServiceAction(EventAction):\n    form_cls = SlackNotifyServiceForm\n    label = u'Send a notification to the {workspace} Slack workspace to {channel} and include tags {tags}'\n\n    def __init__(self, *args, **kwargs):\n        super(SlackNotifyServiceAction, self).__init__(*args, **kwargs)\n        self.form_fields = {\n            'workspace': {\n                'type': 'choice',\n                'choices': [(i.id, i.name) for i in self.get_integrations()]\n            },\n            'channel': {\n                'type': 'string',\n                'placeholder': 'i.e #critical'\n            },\n            'tags': {\n                'type': 'string',\n                'placeholder': 'i.e environment,user,my_tag'\n            }\n        }\n\n    def is_enabled(self):\n        return self.get_integrations().exists()\n\n    def after(self, event, state):\n        if event.group.is_ignored():\n            return\n\n        integration_id = self.get_option('workspace')\n        channel = self.get_option('channel_id')\n        tags = set(self.get_tags_list())\n\n        try:\n            integration = Integration.objects.get(\n                provider='slack',\n                organizations=self.project.organization,\n                id=integration_id\n            )\n        except Integration.DoesNotExist:\n            # Integration removed, rule still active.\n            return\n\n        def send_notification(event, futures):\n            rules = [f.rule for f in futures]\n            attachment = build_attachment(event.group, event=event, tags=tags, rules=rules)\n\n            payload = {\n                'token': integration.metadata['access_token'],\n                'channel': channel,\n                'attachments': json.dumps([attachment]),\n            }\n\n            session = http.build_session()\n            resp = session.post('https://slack.com/api/chat.postMessage', data=payload)\n            resp.raise_for_status()\n            resp = resp.json()\n            if not resp.get('ok'):\n                self.logger.info('rule.fail.slack_post', extra={'error': resp.get('error')})\n\n        key = u'slack:{}:{}'.format(integration_id, channel)\n\n        metrics.incr('notifications.sent', instance='slack.notification')\n        yield self.future(send_notification, key=key)\n\n    def render_label(self):\n        try:\n            integration_name = Integration.objects.get(\n                provider='slack',\n                organizations=self.project.organization,\n                id=self.get_option('workspace')\n            ).name\n        except Integration.DoesNotExist:\n            integration_name = '[removed]'\n\n        tags = self.get_tags_list()\n\n        return self.label.format(\n            workspace=integration_name,\n            channel=self.get_option('channel'),\n            tags=u'[{}]'.format(', '.join(tags)),\n        )\n\n    def get_tags_list(self):\n        return [s.strip() for s in self.get_option('tags', '').split(',')]\n\n    def get_integrations(self):\n        return Integration.objects.filter(\n            provider='slack',\n            organizations=self.project.organization,\n        )\n\n    def get_form_instance(self):\n        return self.form_cls(\n            self.data,\n            integrations=self.get_integrations(),\n            channel_transformer=self.get_channel_id,\n        )\n\n    def get_channel_id(self, integration_id, name):\n        try:\n            integration = Integration.objects.get(\n                provider='slack',\n                organizations=self.project.organization,\n                id=integration_id,\n            )\n        except Integration.DoesNotExist:\n            return None\n\n        session = http.build_session()\n\n        token_payload = {\n            'token': integration.metadata['access_token'],\n        }\n\n        # Get slack app resource permissions\n        resp = session.get('https://slack.com/api/apps.permissions.info', params=token_payload)\n        resp = resp.json()\n        if not resp.get('ok'):\n            extra = {'error': resp.get('error')}\n            self.logger.info('rule.slack.permission_check_failed', extra=extra)\n            return None\n\n        channel_perms = resp['info']['channel']['resources']\n        dm_perms = resp['info']['im']['resources']\n\n        # Look for channel ID\n        channels_payload = dict(token_payload, **{\n            'exclude_archived': False,\n            'exclude_members': True,\n        })\n\n        resp = session.get('https://slack.com/api/channels.list', params=channels_payload)\n        resp = resp.json()\n        if not resp.get('ok'):\n            self.logger.info('rule.slack.channel_list_failed', extra={'error': resp.get('error')})\n            return None\n\n        channel_id = {c['name']: c['id'] for c in resp['channels']}.get(name)\n\n        if channel_id:\n            if channel_id in channel_perms['excluded_ids']:\n                return None\n\n            if not channel_perms['wildcard'] and channel_id not in channel_perms['ids']:\n                return None\n\n            return (CHANNEL_PREFIX, channel_id)\n\n        # Look for user ID\n        resp = session.get('https://slack.com/api/users.list', params=token_payload)\n        resp = resp.json()\n        if not resp.get('ok'):\n            self.logger.info('rule.slack.user_list_failed', extra={'error': resp.get('error')})\n            return None\n\n        member_id = {c['name']: c['id'] for c in resp['members']}.get(name)\n\n        if member_id and member_id in dm_perms['ids']:\n            return (MEMBER_PREFIX, member_id)\n\n        return None\n", "path": "src/sentry/integrations/slack/notify_action.py"}], "after_files": [{"content": "from __future__ import absolute_import\n\nfrom sentry import http\nfrom sentry.integrations import Integration, IntegrationMetadata\nfrom sentry.utils.pipeline import NestedPipelineView\nfrom sentry.identity.pipeline import IdentityProviderPipeline\nfrom sentry.utils.http import absolute_uri\n\nDESCRIPTION = \"\"\"\nDefine a relationship between Sentry and your Slack workspace(s).\n\n * Unfurls Sentry URLs in slack, providing context and actionability on issues\n   directly within your Slack workspace.\n * Resolve, ignore, and assign issues with minimal context switching.\n * Configure rule based Slack notifications to automatically be posted into the\n   specified channel.\n\"\"\"\n\nalert_link = {\n    'text': 'Looking to send Sentry alerts to Slack? Add an **Alert Rule** for this project.',\n    'link': '/settings/{orgId}/{projectId}/alerts/rules/'\n}\n\nmetadata = IntegrationMetadata(\n    description=DESCRIPTION.strip(),\n    author='The Sentry Team',\n    issue_url='https://github.com/getsentry/sentry/issues/new?title=Slack%20Integration:%20&labels=Component%3A%20Integrations',\n    source_url='https://github.com/getsentry/sentry/tree/master/src/sentry/integrations/slack',\n    aspects={\n        'alert_link': alert_link,\n    }\n)\n\n\nclass SlackIntegration(Integration):\n    key = 'slack'\n    name = 'Slack'\n    metadata = metadata\n\n    identity_oauth_scopes = frozenset([\n        'channels:read',\n        'groups:read',\n        'users:read',\n        'chat:write',\n        'links:read',\n        'links:write',\n        'team:read',\n    ])\n\n    setup_dialog_config = {\n        'width': 600,\n        'height': 900,\n    }\n\n    def get_pipeline_views(self):\n        identity_pipeline_config = {\n            'oauth_scopes': self.identity_oauth_scopes,\n            'redirect_url': absolute_uri('/extensions/slack/setup/'),\n        }\n\n        identity_pipeline_view = NestedPipelineView(\n            bind_key='identity',\n            provider_key='slack',\n            pipeline_cls=IdentityProviderPipeline,\n            config=identity_pipeline_config,\n        )\n\n        return [identity_pipeline_view]\n\n    def get_team_info(self, access_token):\n        payload = {\n            'token': access_token,\n        }\n\n        session = http.build_session()\n        resp = session.get('https://slack.com/api/team.info', params=payload)\n        resp.raise_for_status()\n        resp = resp.json()\n\n        return resp['team']\n\n    def build_integration(self, state):\n        data = state['identity']['data']\n        assert data['ok']\n\n        scopes = sorted(self.identity_oauth_scopes)\n        team_data = self.get_team_info(data['access_token'])\n\n        return {\n            'name': data['team_name'],\n            'external_id': data['team_id'],\n            'metadata': {\n                'access_token': data['access_token'],\n                'scopes': scopes,\n                'icon': team_data['icon']['image_132'],\n                'domain_name': team_data['domain'] + '.slack.com',\n            },\n            'user_identity': {\n                'type': 'slack',\n                'external_id': data['installer_user_id'],\n                'scopes': [],\n                'data': {},\n            },\n        }\n", "path": "src/sentry/integrations/slack/integration.py"}, {"content": "from __future__ import absolute_import\n\nfrom django import forms\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom sentry import http\nfrom sentry.rules.actions.base import EventAction\nfrom sentry.utils import metrics, json\nfrom sentry.models import Integration\n\nfrom .utils import build_attachment\n\nMEMBER_PREFIX = '@'\nCHANNEL_PREFIX = '#'\nstrip_channel_chars = ''.join([MEMBER_PREFIX, CHANNEL_PREFIX])\n\n\nclass SlackNotifyServiceForm(forms.Form):\n    workspace = forms.ChoiceField(choices=(), widget=forms.Select(\n    ))\n    channel = forms.CharField(widget=forms.TextInput())\n    channel_id = forms.HiddenInput()\n    tags = forms.CharField(required=False, widget=forms.TextInput())\n\n    def __init__(self, *args, **kwargs):\n        # NOTE: Workspace maps directly to the integration ID\n        workspace_list = [(i.id, i.name) for i in kwargs.pop('integrations')]\n        self.channel_transformer = kwargs.pop('channel_transformer')\n\n        super(SlackNotifyServiceForm, self).__init__(*args, **kwargs)\n\n        if workspace_list:\n            self.fields['workspace'].initial = workspace_list[0][0]\n\n        self.fields['workspace'].choices = workspace_list\n        self.fields['workspace'].widget.choices = self.fields['workspace'].choices\n\n    def clean(self):\n        cleaned_data = super(SlackNotifyServiceForm, self).clean()\n\n        workspace = cleaned_data.get('workspace')\n        channel = cleaned_data.get('channel', '').lstrip(strip_channel_chars)\n\n        channel_id = self.channel_transformer(workspace, channel)\n\n        if channel_id is None and workspace is not None:\n            params = {\n                'channel': channel,\n                'workspace': dict(self.fields['workspace'].choices).get(int(workspace)),\n            }\n\n            raise forms.ValidationError(\n                _('The slack resource \"%(channel)s\" does not exist or has not been granted access in the %(workspace)s Slack workspace.'),\n                code='invalid',\n                params=params,\n            )\n\n        channel_prefix, channel_id = channel_id\n        cleaned_data['channel'] = channel_prefix + channel\n        cleaned_data['channel_id'] = channel_id\n\n        return cleaned_data\n\n\nclass SlackNotifyServiceAction(EventAction):\n    form_cls = SlackNotifyServiceForm\n    label = u'Send a notification to the {workspace} Slack workspace to {channel} and include tags {tags}'\n\n    def __init__(self, *args, **kwargs):\n        super(SlackNotifyServiceAction, self).__init__(*args, **kwargs)\n        self.form_fields = {\n            'workspace': {\n                'type': 'choice',\n                'choices': [(i.id, i.name) for i in self.get_integrations()]\n            },\n            'channel': {\n                'type': 'string',\n                'placeholder': 'i.e #critical'\n            },\n            'tags': {\n                'type': 'string',\n                'placeholder': 'i.e environment,user,my_tag'\n            }\n        }\n\n    def is_enabled(self):\n        return self.get_integrations().exists()\n\n    def after(self, event, state):\n        if event.group.is_ignored():\n            return\n\n        integration_id = self.get_option('workspace')\n        channel = self.get_option('channel_id')\n        tags = set(self.get_tags_list())\n\n        try:\n            integration = Integration.objects.get(\n                provider='slack',\n                organizations=self.project.organization,\n                id=integration_id\n            )\n        except Integration.DoesNotExist:\n            # Integration removed, rule still active.\n            return\n\n        def send_notification(event, futures):\n            rules = [f.rule for f in futures]\n            attachment = build_attachment(event.group, event=event, tags=tags, rules=rules)\n\n            payload = {\n                'token': integration.metadata['access_token'],\n                'channel': channel,\n                'attachments': json.dumps([attachment]),\n            }\n\n            session = http.build_session()\n            resp = session.post('https://slack.com/api/chat.postMessage', data=payload)\n            resp.raise_for_status()\n            resp = resp.json()\n            if not resp.get('ok'):\n                self.logger.info('rule.fail.slack_post', extra={'error': resp.get('error')})\n\n        key = u'slack:{}:{}'.format(integration_id, channel)\n\n        metrics.incr('notifications.sent', instance='slack.notification')\n        yield self.future(send_notification, key=key)\n\n    def render_label(self):\n        try:\n            integration_name = Integration.objects.get(\n                provider='slack',\n                organizations=self.project.organization,\n                id=self.get_option('workspace')\n            ).name\n        except Integration.DoesNotExist:\n            integration_name = '[removed]'\n\n        tags = self.get_tags_list()\n\n        return self.label.format(\n            workspace=integration_name,\n            channel=self.get_option('channel'),\n            tags=u'[{}]'.format(', '.join(tags)),\n        )\n\n    def get_tags_list(self):\n        return [s.strip() for s in self.get_option('tags', '').split(',')]\n\n    def get_integrations(self):\n        return Integration.objects.filter(\n            provider='slack',\n            organizations=self.project.organization,\n        )\n\n    def get_form_instance(self):\n        return self.form_cls(\n            self.data,\n            integrations=self.get_integrations(),\n            channel_transformer=self.get_channel_id,\n        )\n\n    def get_channel_id(self, integration_id, name):\n        try:\n            integration = Integration.objects.get(\n                provider='slack',\n                organizations=self.project.organization,\n                id=integration_id,\n            )\n        except Integration.DoesNotExist:\n            return None\n\n        session = http.build_session()\n\n        token_payload = {\n            'token': integration.metadata['access_token'],\n        }\n\n        # Get slack app resource permissions\n        resp = session.get('https://slack.com/api/apps.permissions.info', params=token_payload)\n        resp = resp.json()\n        if not resp.get('ok'):\n            extra = {'error': resp.get('error')}\n            self.logger.info('rule.slack.permission_check_failed', extra=extra)\n            return None\n\n        channel_perms = resp['info']['channel']['resources']\n        dm_perms = resp['info']['im']['resources']\n\n        # Look for channel ID\n        channels_payload = dict(token_payload, **{\n            'exclude_archived': False,\n            'exclude_members': True,\n        })\n\n        resp = session.get('https://slack.com/api/channels.list', params=channels_payload)\n        resp = resp.json()\n        if not resp.get('ok'):\n            self.logger.info('rule.slack.channel_list_failed', extra={'error': resp.get('error')})\n            return None\n\n        channel_id = {c['name']: c['id'] for c in resp['channels']}.get(name)\n\n        # Channel may be private, use the groups.list endpoint to find them\n        if not channel_id:\n            resp = session.get('https://slack.com/api/groups.list', params=channels_payload)\n            resp = resp.json()\n            if not resp.get('ok'):\n                self.logger.info('rule.slack.group_list_failed', extra={'error': resp.get('error')})\n                return None\n\n            channel_id = {c['name']: c['id'] for c in resp['groups']}.get(name)\n\n        # We should sufficiently have been able to find the channel by now\n        if channel_id:\n            if channel_id in channel_perms['excluded_ids']:\n                return None\n\n            if not channel_perms['wildcard'] and channel_id not in channel_perms['ids']:\n                return None\n\n            return (CHANNEL_PREFIX, channel_id)\n\n        # Look for user ID\n        resp = session.get('https://slack.com/api/users.list', params=token_payload)\n        resp = resp.json()\n        if not resp.get('ok'):\n            self.logger.info('rule.slack.user_list_failed', extra={'error': resp.get('error')})\n            return None\n\n        member_id = {c['name']: c['id'] for c in resp['members']}.get(name)\n\n        if member_id and member_id in dm_perms['ids']:\n            return (MEMBER_PREFIX, member_id)\n\n        return None\n", "path": "src/sentry/integrations/slack/notify_action.py"}]}