Browse Source

add slack to social auth (#4705)

* add slack to social auth

* change scope to support webhooks

* update changes to correct version
Jess MacQueen 7 years ago
parent
commit
8bc8a5a38c
3 changed files with 78 additions and 1 deletions
  1. 1 0
      CHANGES
  2. 5 1
      src/sentry/conf/server.py
  3. 72 0
      src/social_auth/backends/slack.py

+ 1 - 0
CHANGES

@@ -13,6 +13,7 @@ Version 8.18 (Unreleased)
 - Moved "create organization" into React.
 - Expanded React Form components (Form, ApiForm).
 - Moved "create team" into React.
+- add Slack to supported auth backends in social auth (for plugins)
 
 Schema Changes
 ~~~~~~~~~~~~~~

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

@@ -344,6 +344,7 @@ AUTHENTICATION_BACKENDS = (
     'social_auth.backends.bitbucket.BitbucketBackend',
     'social_auth.backends.trello.TrelloBackend',
     'social_auth.backends.asana.AsanaBackend',
+    'social_auth.backends.slack.SlackBackend',
 )
 
 AUTH_PASSWORD_VALIDATORS = [
@@ -368,6 +369,7 @@ SOCIAL_AUTH_AUTHENTICATION_BACKENDS = (
     'social_auth.backends.bitbucket.BitbucketBackend',
     'social_auth.backends.trello.TrelloBackend',
     'social_auth.backends.asana.AsanaBackend',
+    'social_auth.backends.slack.SlackBackend',
 )
 
 SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
@@ -408,13 +410,15 @@ AUTH_PROVIDERS = {
     'trello': ('TRELLO_API_KEY', 'TRELLO_API_SECRET'),
     'bitbucket': ('BITBUCKET_CONSUMER_KEY', 'BITBUCKET_CONSUMER_SECRET'),
     'asana': ('ASANA_CLIENT_ID', 'ASANA_CLIENT_SECRET'),
+    'slack': ('SLACK_CLIENT_ID', 'SLACK_CLIENT_SECRET'),
 }
 
 AUTH_PROVIDER_LABELS = {
     'github': 'GitHub',
     'trello': 'Trello',
     'bitbucket': 'Bitbucket',
-    'asana': 'Asana'
+    'asana': 'Asana',
+    'slack': 'Slack'
 }
 
 import random

+ 72 - 0
src/social_auth/backends/slack.py

@@ -0,0 +1,72 @@
+"""
+Obtain
+SLACK_CLIENT_ID & SLACK_CLIENT_SECRET
+and put into sentry.conf.py
+"""
+from __future__ import absolute_import
+
+import requests
+
+from social_auth.backends import BaseOAuth2, OAuthBackend
+
+SLACK_TOKEN_EXCHANGE_URL = 'https://slack.com/api/oauth.access'
+SLACK_AUTHORIZATION_URL = 'https://slack.com/oauth/authorize'
+SLACK_USER_DETAILS_URL = 'https://slack.com/api/auth.test'
+
+
+class SlackBackend(OAuthBackend):
+    """Slack OAuth authentication backend"""
+    name = 'slack'
+    EXTRA_DATA = [
+        ('email', 'email'),
+        ('name', 'full_name'),
+        ('id', 'id'),
+        ('refresh_token', 'refresh_token')
+    ]
+
+    def get_user_details(self, response):
+        """Return user details from Slack account"""
+
+        return {
+            'email': response.get('email'),
+            'id': response.get('id'),
+            'full_name': response.get('name')
+        }
+
+
+class SlackAuth(BaseOAuth2):
+    """Slack OAuth authentication mechanism"""
+    AUTHORIZATION_URL = SLACK_AUTHORIZATION_URL
+    ACCESS_TOKEN_URL = SLACK_TOKEN_EXCHANGE_URL
+    AUTH_BACKEND = SlackBackend
+    SETTINGS_KEY_NAME = 'SLACK_CLIENT_ID'
+    SETTINGS_SECRET_NAME = 'SLACK_CLIENT_SECRET'
+    REDIRECT_STATE = False
+    DEFAULT_SCOPE = ['incoming-webhook']
+
+    def user_data(self, access_token, *args, **kwargs):
+        """Loads user data from service"""
+        try:
+            resp = requests.get(SLACK_USER_DETAILS_URL,
+                                params={'token': access_token})
+            resp.raise_for_status()
+            content = resp.json()
+            return {
+                'id': content['user_id'],
+                'name': content['user']
+            }
+        except ValueError:
+            return None
+
+    @classmethod
+    def refresh_token(cls, token):
+        params = cls.refresh_token_params(token)
+        response = requests.post(cls.ACCESS_TOKEN_URL, data=params,
+                                 headers=cls.auth_headers())
+        response.raise_for_status()
+        return response.json()
+
+# Backend definition
+BACKENDS = {
+    'slack': SlackAuth,
+}