Skip to content

Commit

Permalink
Use bot & user token for full functionality with newer bots
Browse files Browse the repository at this point in the history
Allow passing both the bot and user token to the scripts via the environment
variables DESTALINATOR_API_BOT_TOKEN and DESTALINATOR_API_USER_TOKEN. They have
different permissions and the bot token is only used to post messags, all other
calls use the user token. Fixes #194.

This also includes #210 and fixes #209 by moving the tokens into the header
instead of using query parameters.

Don't pass `as_user` to Slack, as this has been removed.
  • Loading branch information
christofdamian committed Apr 14, 2021
1 parent 77fe3dc commit 3e0f48a
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 27 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ All configs in `configuration.yaml` are overrideable through environment variabl
1. Make sure [the Slackbot app](https://slack.com/apps/A0F81R8ET-slackbot) is installed for your Slack
2. Add a Slackbot integration, and copy the `token` parameter from the URL provided

#### `DESTALINATOR_API_TOKEN` (Required)
#### `DESTALINATOR_API_BOT_TOKEN` and `DESTALINATOR_API_USER_TOKEN`(Required)

The best way to get an `API_TOKEN` is to [create a new Slack App](https://api.slack.com/apps/new).
The best way to get the `API BOT_TOKEN` and `API_USER_TOKEN` is to [create a new Slack App](https://api.slack.com/apps/new).

Once you create and name your app on your team, go to "OAuth & Permissions" to give it the following permission scopes:

Expand All @@ -104,7 +104,7 @@ Once you create and name your app on your team, go to "OAuth & Permissions" to g
- `emoji:read`
- `users:read`

After saving, you can copy the OAuth Access Token value from the top of the same screen. It probably starts with `xox`.
After saving, you can copy the OAuth Access Tokens value from the top of the same screen. It probably starts with `xox`.

#### `DESTALINATOR_ACTIVATED` (Required)

Expand Down
2 changes: 1 addition & 1 deletion executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, slackbot_injected=None, slacker_injected=None):

self.logger.debug("activated is %s", self.config.activated)

self.slacker = slacker_injected or slacker.Slacker(self.config.slack_name, token=self.config.api_token)
self.slacker = slacker_injected or slacker.Slacker(self.config.slack_name, bot_token=self.config.api_bot_token, user_token=self.config.api_user_token)

self.ds = destalinator.Destalinator(slacker=self.slacker,
slackbot=self.slackbot,
Expand Down
25 changes: 15 additions & 10 deletions slacker.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@

class Slacker(WithLogger, WithConfig):

def __init__(self, slack_name, token, init=True):
def __init__(self, slack_name, bot_token, user_token, init=True):
"""
slack name is the short name of the slack (preceding '.slack.com')
token should be a Slack API Token.
bot_token should be a Slack API Bot Token.
user_token should be a Slack API User Token.
"""
self.slack_name = slack_name
self.token = token
assert self.token, "Token should not be blank"
self.url = self.api_url()
self.session = requests.Session()
self.session.headers.update({"Authorization": "Bearer " + token})

assert bot_token, "Bot Token should not be blank"
self.bot_session = requests.Session()
self.bot_session.headers.update({"Authorization": "Bearer " + bot_token})

assert user_token, "User Token should not be blank"
self.user_session = requests.Session()
self.user_session.headers.update({"Authorization": "Bearer " + user_token})

if init:
self.get_users()
self.get_channels()
Expand Down Expand Up @@ -57,7 +63,7 @@ def get_with_retry_to_json(self, url):
max_retry_attempts = 10
payload = None
while not payload:
response = self.session.get(url)
response = self.user_session.get(url)

try:
response.raise_for_status()
Expand Down Expand Up @@ -292,7 +298,7 @@ def archive(self, channel_name):
url_template = self.url + "conversations.archive?channel={}"
cid = self.get_channelid(channel_name)
url = url_template.format(cid)
request = self.session.post(url)
request = self.user_session.post(url)
payload = request.json()
return payload

Expand All @@ -315,7 +321,6 @@ def post_message(self, channel, message, message_type=None):
bot_name = self.config.bot_name
bot_avatar_url = self.config.bot_avatar_url
if bot_name or bot_avatar_url:
post_data['as_user'] = False
if bot_name:
post_data['username'] = bot_name
if bot_avatar_url:
Expand All @@ -324,5 +329,5 @@ def post_message(self, channel, message, message_type=None):
if message_type:
post_data['attachments'] = json.dumps([{'fallback': message_type}])

p = self.session.post(self.url + "chat.postMessage", data=post_data)
p = self.bot_session.post(self.url + "chat.postMessage", data=post_data)
return p.json()
2 changes: 1 addition & 1 deletion tests/mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def mocked_slackbot_object():


def mocked_slacker_object(channels_list=None, users_list=None, messages_list=None, emoji_list=None):
slacker_obj = slacker.Slacker(get_config().slack_name, token='token', init=False)
slacker_obj = slacker.Slacker(get_config().slack_name, bot_token='bot_token', user_token='user_token', init=False)

slacker_obj.get_all_channel_objects = mock.MagicMock(return_value=channels_list or [])
slacker_obj.get_channels()
Expand Down
24 changes: 12 additions & 12 deletions tests/test_destalinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def get_channels(self):

class DestalinatorChannelMarkupTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch('tests.test_destalinator.SlackerMock')
Expand Down Expand Up @@ -156,7 +156,7 @@ def test_add_slack_channel_markup_ignore_screaming(self, mock_slacker):

class DestalinatorChannelMinimumAgeTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch('tests.test_destalinator.SlackerMock')
Expand Down Expand Up @@ -184,7 +184,7 @@ def test_channel_is_young(self, mock_slacker):

class DestalinatorGetEarliestArchiveDateTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

# TODO: This test (and others) would be redundant with solid testing around config directly.
Expand All @@ -210,7 +210,7 @@ def test_falls_back_to_past_date(self):

class DestalinatorGetMessagesTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch('tests.test_destalinator.SlackerMock')
Expand Down Expand Up @@ -245,7 +245,7 @@ def test_with_limited_included_subtypes(self, mock_slacker):

class DestalinatorIgnoreChannelTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch.object(get_config(), 'ignore_channels', ['stalinists'])
Expand Down Expand Up @@ -288,7 +288,7 @@ def test_with_empty_ignore_channel_config(self):

class DestalinatorPostMarkedUpMessageTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

def test_with_a_string_having_a_channel(self):
Expand Down Expand Up @@ -319,7 +319,7 @@ def test_with_a_string_having_no_channels(self):

class DestalinatorStaleTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch('tests.test_destalinator.SlackerMock')
Expand Down Expand Up @@ -379,7 +379,7 @@ def test_with_only_an_attachment_message(self, mock_slacker):

class DestalinatorArchiveTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch.object(get_config(), 'ignore_channels', ['stalinists'])
Expand Down Expand Up @@ -442,7 +442,7 @@ def test_handles_a_bad_archive_api_response(self, mock_slacker):

class DestalinatorSafeArchiveTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch('tests.test_destalinator.SlackerMock')
Expand Down Expand Up @@ -477,7 +477,7 @@ def test_calls_archive_method(self, mock_slacker):

class DestalinatorSafeArchiveAllTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch('tests.test_destalinator.SlackerMock')
Expand Down Expand Up @@ -534,7 +534,7 @@ def fake_stale(channel, days):

class DestalinatorWarnTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch('tests.test_destalinator.SlackerMock')
Expand Down Expand Up @@ -603,7 +603,7 @@ def test_does_not_warn_when_previous_warning_with_changed_text_found(self, mock_

class DestalinatorWarnAllTestCase(unittest.TestCase):
def setUp(self):
self.slacker = SlackerMock("testing", "token")
self.slacker = SlackerMock("testing", "bot_token", "user_token")
self.slackbot = slackbot.Slackbot("testing", "token")

@mock.patch('tests.test_destalinator.SlackerMock')
Expand Down

0 comments on commit 3e0f48a

Please sign in to comment.