Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to use Bot Tokens #211

Merged
merged 3 commits into from
Jun 1, 2020
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
65 changes: 9 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,65 +95,18 @@ urlpatterns = [

## 1. Create a Slack App

- Navigate to [https://api.slack.com/apps](https://api.slack.com/apps) and click `Create New App`.
- Give it a name, e.g. 'Response', and select the relevant workspace.

- In the OAuth and Permissions page, scroll down to scopes.

- Add the following scopes:
- `app_mentions:read`
- `channels:history`
- `channels:join`
- `channels:manage`
- `channels:read`
- `channels:write`
- `chat:write:bot`
- `chat:write:user`
- `reactions:write`
- `users:read`
- `users:read.email`

- At the top of the page, the `Install App to Workspace` button is now available. Click it!
Follow [these instructions](./docs/slack_app_create.md) to create a new Slack App.

## 2. Update your `settings.py`

### Base site address (`SITE_URL`)

Response needs to know where it is running in order to create links to the UI in Slack. Whilst running locally, you might want this set to something like `http://localhost:8000`.

### OAuth Access Token (`SLACK_TOKEN`)

Response needs an OAuth access token to use the Slack API.

- Copy the token that starts `xoxp-...` from the OAuth & Permissions section of your Slack App and use it to set the `SLACK_TOKEN` variable.

**Note:** Since some of the APIs commands we use require a _user_ token, we only need the token starting with `xoxp-...`. If/when Slack allow these actions to be controlled by Bots, we can use the _bot_ token, starting `xoxb-...`.

### Slack Client (`SLACK_CLIENT`)

Response needs a shared global instance of a Slack Client to talk to the Slack API. Typically this does not require any additional configuration.

```python
from response.slack.client import SlackClient
SLACK_CLIENT = SlackClient(SLACK_TOKEN)
```

### Signing Secret (`SLACK_SIGNING_SECRET`)

Response uses the Slack signing secret to restrict access to public endpoints.

- Copy the Signing secret from the Basic Information page and use it to set the `SIGNING SECRET` variable.

### Incident Channel and ID (`INCIDENT_CHANNEL_NAME`, `INCIDENT_CHANNEL_ID`)

When an incident is declared, a 'headline' post is sent to a central channel.

See the demo app for an example of how to get the incident channel ID from the Slack API.

### Bot Name and ID (`INCIDENT_BOT_NAME`, `INCIDENT_BOT_ID`)

We want to invite the Bot to all Incident Channels, so need to know its ID.

| Environment Variable | Descriptions |
|---|---|
| `SLACK_TOKEN` | Response needs an OAuth access token to use the Slack API.<br /><br />Copy the Bot Token that starts `xoxb-...` from the OAuth & Permissions section of your Slack App and use it to set the `SLACK_TOKEN` variable.|
| `SITE_URL` | Response needs to know where it is running in order to create links to the UI in Slack. Whilst running locally, you might want this set to something like `http://localhost:8000`. |
| `SLACK_SIGNING_SECRET` | Response uses the Slack signing secret to restrict access to public endpoints.<br /><br />Copy the Signing secret from the Basic Information page and use it to set the `SIGNING SECRET` variable. |
| `INCIDENT_CHANNEL_ID` | When an incident is declared, a 'headline' post is sent to a central channel.<br /><br />See the [demo app settings](./demo/demo/settings/dev.py) for an example of how to get the incident channel ID from the Slack API. |
| `INCIDENT_BOT_ID` | We want to invite the Bot to all Incident Channels, so need to know its ID.<br /><br />See the [demo app settings](./demo/demo/settings/dev.py) for an example of how to get the bot ID from the Slack API. |
| `SLACK_CLIENT` | Response needs a shared global instance of a Slack Client to talk to the Slack API. Typically this does not require any additional configuration. <br /><pre>from response.slack.client import SlackClient<br />SLACK_CLIENT = SlackClient(SLACK_TOKEN)</pre> |

## 3. Running the server

Expand Down
58 changes: 14 additions & 44 deletions demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,7 @@ Broadly speaking, this sets things up as below:

## 1. Create a Slack App

- Navigate to [https://api.slack.com/apps](https://api.slack.com/apps) and click `Create New App`.
- Give it a name, e.g. 'Response', and select the relevant workspace.

- In the OAuth and Permissions page, scroll down to scopes.

- Add the following scopes:
- `channels:history`
- `channels:read`
- `channels:write`
- `chat:write:bot`
- `chat:write:user`
- `users:read`
- `reactions:write`

- At the top of the page, the `Install App to Workspace` button is now available. Click it!
Follow [these instructions](../docs/slack_app_create.md) to create a new Slack App.

## 2. Configure the demo app

Expand All @@ -41,31 +27,12 @@ $ cp env.example .env
```
and update the variables in it:

### OAuth Access Token (`SLACK_TOKEN`)

Response needs an OAuth access token to use the Slack API.

- Copy the token that starts `xoxp-...` from the OAuth & Permissions section of your Slack App and use it to set the `SLACK_TOKEN` variable.

**Note:** Since some of the APIs commands we use require a _user_ token, we only need the token starting with `xoxp-...`. If/when Slack allow these actions to be controlled by Bots, we can use the _bot_ token, starting `xoxb-...`.

### Signing Secret (`SLACK_SIGNING_SECRET`)

Response uses the Slack signing secret to restrict access to public endpoints.

- Copy the Signing secret from the Basic Information page and use it to set the `SIGNING SECRET` variable.

### Incident Channel (`INCIDENT_CHANNEL_NAME`)

When an incident is declared, a 'headline' post is sent to a central channel.

- The default channel is `incidents` - change `INCIDENT_CHANNEL_NAME` if you want them to be sent somewhere else (note: do not include the #).

### Bot Name (`INCIDENT_BOT_NAME`)

We want to invite the Bot to all Incident Channels, so need to know its ID.

- The default bot name is `incident` - change the `INCIDENT_BOT_NAME` if your app uses something different.
| Environment Variable | Descriptions |
|---|---|
| `SLACK_TOKEN` | Response needs an OAuth access token to use the Slack API.<br /><br />Copy the Bot Token that starts `xoxb-...` from the OAuth & Permissions section of your Slack App and use it to set the `SLACK_TOKEN` variable. |
| `SLACK_SIGNING_SECRET` | Response uses the Slack signing secret to restrict access to public endpoints.<br /><br />Copy the Signing secret from the Basic Information page and use it to set the `SIGNING SECRET` variable. |
| `INCIDENT_CHANNEL_NAME` | When an incident is declared, a 'headline' post is sent to a central channel.<br /><br />The default channel is `incidents` - change `INCIDENT_CHANNEL_NAME` if you want them to be sent somewhere else (note: do not include the #). |
| `INCIDENT_BOT_NAME` | We want to invite the Bot to all Incident Channels, so need to know its ID. You can find/configure this in the App Home section of the Slack App.<br /><br />The default bot name is `incident` - change the `INCIDENT_BOT_NAME` if your app uses something different.<br /><br />⚠️ If your chosen username has ever been used on your Slack workspace, Slack will silently change the underlying username and won't show you the actual name in use anywhere. The easiest way to find the exact name you need to use is to make the API call directly [here](https://api.slack.com/methods/users.list/test), using your bot token from above, and searching the response for you APP ID, which is shown in the Basic Info page. |

## 3. Run Response

Expand All @@ -82,14 +49,20 @@ This starts the following containers:
- cron: a container running cron, configured to hit an endpoint in Response every minute
- ngrok: ngrok in a container, providing a public URL pointed at Response.


You can tail the logs of all containers with:
```
docker-compose logs -f
```

Ngrok establishes a new, random, URL any time it starts. You'll need this to complete the Slack app setup, so look for an entry like this and make note of the https://abc123.ngrok.io address - this is your public URL.

```
ngrok | The ngrok tunnel is active
ngrok | https://6bb315c8.ngrok.io ---> response:8000
```

If everything has started successfully, you should see logs resembling the following:
If everything has started successfully, you should see logs that look like this:

```
response | Django version 2.1.7, using settings 'response.settings.dev'
Expand All @@ -101,13 +74,10 @@ response | Quit the server with CONTROL-C.

Head back to the Slack web UI and complete the configuration of your app, as [described here](../docs/slack_app_config.md).


## 5. Test it's working!

In Slack, start an incident with `/incident Something's happened`. You should see a post in your incidents channel!

- Visit the incident doc by clicking the Doc link.
- Create a comms channel by clicking the button.
- In the comms channel check out the `@incident` commands. You can find the ones available by entering `@incident help`.


1 change: 1 addition & 0 deletions demo/demo/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
},
}

RESPONSE_LOGIN_REQUIRED = False

SLACK_TOKEN = get_env_var("SLACK_TOKEN")
SLACK_SIGNING_SECRET = get_env_var("SLACK_SIGNING_SECRET")
Expand Down
9 changes: 5 additions & 4 deletions docs/slack_app_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ The steps here outline how to complete the Slack side setup for Response. The a

## Event Subscriptions

**Important** You need to have the server running and available to setup events, as Slack sends a challenge request to this address and expects a specific response.

In the Event Subscriptions page we need to configure the following:

- Toggle `Enable Events` to On
- In the Request URL enter: `https://<public-url>/slack/event`
- You need to have the server running and available as Slack sends a challenge to this address and expects a specific response.

- Under the Subscribe to Bot Events section, add the following:
- `app_mention`
Expand All @@ -31,7 +32,7 @@ In the Event Subscriptions page we need to configure the following:

- In the Interactive Components page, enable and set the URL to `https://<public-url>/slack/action`.

## Bot Users

- In the Bot Users page, configure the Display Name and Default Username to `incident`.
- Toggle 'Always Show My Bot as Online' to On.
## Reinstall App

With these changes made, you'll need to reinstall the app to your workspace. There should be a bar at the top with a link, but if not you can find the resinstall link in the OAuth & Permissions page.
20 changes: 20 additions & 0 deletions docs/slack_app_create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Creating your Slack App

- Navigate to [https://api.slack.com/apps](https://api.slack.com/apps) and click `Create New App`.
- Give it a name, e.g. 'Response', and select the relevant workspace.

- In the OAuth and Permissions page, scroll down to Bot Token Scopes.

- Add the following scopes:
- `app_mentions:read`
- `channels:history`
- `channels:join`
- `channels:manage`
- `channels:read`
- `chat:write`
- `chat:write.public`
- `reactions:write`
- `users:read`
- `users:read.email`

- At the top of the page, the `Install App to Workspace` button is now available. Click it!
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ faker>=2.0.0
psycopg2-binary==2.8.2

# Linting
flake8
flake8==3.8.2
isort==4.3.21
pep8
black==19.3b0
Expand Down
6 changes: 0 additions & 6 deletions response/slack/action_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ def handle_create_comms_channel(ac: ActionContext):
except Exception as ex:
logger.error(ex)

# Un-invite the user who owns the Slack token,
# otherwise they'll be added to every incident channel
slack_token_owner = settings.SLACK_CLIENT.get_slack_token_owner()
if ac.incident.reporter != slack_token_owner:
settings.SLACK_CLIENT.leave_channel(comms_channel.channel_id)

# Update the headline post to link to this
headline_post = HeadlinePost.objects.get(incident=ac.incident)
headline_post.comms_channel = comms_channel
Expand Down
8 changes: 4 additions & 4 deletions response/slack/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def api_call(self, api_endpoint, *args, **kwargs):
return response

def users_list(self):
logger.info(f"Listing Slack users")
logger.info("Listing Slack users")
return self.api_call("users.list")

def get_paginated_users(self, limit=0, cursor=None):
Expand Down Expand Up @@ -106,7 +106,7 @@ def get_channel_id(self, name, auto_unarchive=False):
logger.info(f"get_channel_id - next_cursor == [{next_cursor}]")
except LookupError:
logger.error(
f"get_channel_id - I guess checking next_cursor in response object didn't work."
"get_channel_id - I guess checking next_cursor in response object didn't work."
)

for channel in response["channels"]:
Expand Down Expand Up @@ -146,7 +146,7 @@ def create_channel(self, name):
return response["channel"]["id"]
except KeyError:
raise SlackError(
f"Got unexpected response from Slack API for channels.create - couldn't find channel.id key"
"Got unexpected response from Slack API for channels.create - couldn't find channel.id key"
)

def get_or_create_channel(self, channel_name, auto_unarchive=False):
Expand Down Expand Up @@ -223,7 +223,7 @@ def get_slack_token_owner(self):
return self.api_call("auth.test")["user_id"]
except KeyError:
raise SlackError(
f"Got unexpected response from Slack API for auth.test - couldn't find user_id key"
"Got unexpected response from Slack API for auth.test - couldn't find user_id key"
)

def invite_user_to_channel(self, user_id, channel_id):
Expand Down
2 changes: 1 addition & 1 deletion response/slack/decorators/event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def handle_event(payload):

# ignore bot messages
if event.get("subtype", None) == "bot_message":
logger.info(f"Ignoring bot message")
logger.info("Ignoring bot message")
return

# if it doesn't exist, error and return
Expand Down
2 changes: 1 addition & 1 deletion response/slack/dialog_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def __init__(
):
super().__init__(label, name, optional, hint, subtype, value, placeholder)
self.type = "select"
self.options = [{"label": l, "value": v} for l, v in options]
self.options = [{"label": lbl, "value": val} for lbl, val in options]


class SelectFromUsers(Element):
Expand Down
2 changes: 1 addition & 1 deletion response/slack/incident_commands/incident_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def close_incident(incident: Incident, user_id: str, message: str):
incident.end_time = datetime.now()
incident.save()

comms_channel.post_in_channel(f"This incident has been closed! 📖 ⟶ 📕")
comms_channel.post_in_channel("This incident has been closed! 📖 ⟶ 📕")

return True, None

Expand Down
2 changes: 1 addition & 1 deletion response/slack/models/comms_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def rename(self, new_name):
)
raise e
else:
logger.info(f"Attempted to rename channel to nothing. No action take.")
logger.info("Attempted to rename channel to nothing. No action take.")

def __str__(self):
return self.incident.report