oncall-engine/engine/apps/social_auth/pipeline.py
Joey Orlando dcf08425eb
Fix few minor Slack connection issues (#3327)
# What this PR does

Closes https://github.com/grafana/oncall-private/issues/2289

- Fix issue where if you try connecting your Slack user to your OnCall
user and the first time around you encounter an error (ex. connecting to
the wrong Slack workspace), you will see the same error banner message
despite a successful connection. Now we clear the session upon
successful connection to ensure that you will not see any previously
encountered errors.
- Fix some alignment issues on the Slack connection buttons
  **Before**

<img width="564" alt="Screenshot 2023-11-10 at 15 07 48"
src="https://github.com/grafana/oncall/assets/9406895/1a256ae9-668c-4a76-b101-cb4e0af29c43">
<img width="789" alt="Screenshot 2023-11-10 at 15 16 22"
src="https://github.com/grafana/oncall/assets/9406895/61649d9d-2f11-497a-9f2d-274c6dbe90eb">

  **After**

<img width="470" alt="Screenshot 2023-11-10 at 15 10 28"
src="https://github.com/grafana/oncall/assets/9406895/92c0f1d3-75eb-4de2-a3d3-4e014489f970">
<img width="738" alt="Screenshot 2023-11-10 at 15 16 42"
src="https://github.com/grafana/oncall/assets/9406895/dc825266-c45e-4c48-af78-0cd5c3c5b64e">

- On the "User Info" user settings modal tab, render `display_name`
instead of `slack_login`. Currently we prefix `@` before `slack_login`,
which is a bit confusing as it makes you think that this is the handle
you would use to `@` your user in Slack. `display_name` corresponds to
the handle that would be used to `@` your user

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-11-13 12:44:54 +00:00

118 lines
5.1 KiB
Python

import logging
from urllib.parse import urljoin
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponse
from rest_framework import status
from social_core.exceptions import AuthForbidden
from apps.slack.tasks import populate_slack_channels_for_team, populate_slack_usergroups_for_team
from apps.social_auth.exceptions import InstallMultiRegionSlackException
from common.constants.slack_auth import SLACK_AUTH_SLACK_USER_ALREADY_CONNECTED_ERROR, SLACK_AUTH_WRONG_WORKSPACE_ERROR
from common.insight_log import ChatOpsEvent, ChatOpsTypePlug, write_chatops_insight_log
from common.oncall_gateway import check_slack_installation_possible, create_slack_connector
logger = logging.getLogger(__name__)
def set_user_and_organization_from_request(backend, strategy, *args, **kwargs):
user = strategy.request.user
organization = strategy.request.auth.organization
if user is None or organization is None:
return HttpResponse(str(AuthForbidden(backend)), status=status.HTTP_401_UNAUTHORIZED)
return {
"user": user,
"organization": organization,
}
def connect_user_to_slack(response, backend, strategy, user, organization, *args, **kwargs):
from apps.slack.models import SlackUserIdentity
# Continue pipeline step only if it was installation
if backend.name != "slack-login":
return
slack_team_identity = organization.slack_team_identity
slack_user_id = response["authed_user"]["id"]
redirect_to = "/a/grafana-oncall-app/users/me/"
base_url_to_redirect = urljoin(organization.grafana_url, redirect_to)
if slack_team_identity is None:
# means that organization doesn't have slack integration, so user cannot connect their account to slack
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
if slack_team_identity.slack_id != response["team"]["id"]:
# means that user authed in another slack workspace that is not connected to their organization
# change redirect url to show user error message and save it in session param
url = base_url_to_redirect + f"?slack_error={SLACK_AUTH_WRONG_WORKSPACE_ERROR}"
strategy.session[REDIRECT_FIELD_NAME] = url
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
if organization.users.filter(slack_user_identity__slack_id=slack_user_id).exists():
# means that slack user has already been connected to another user in current organization
url = base_url_to_redirect + f"?slack_error={SLACK_AUTH_SLACK_USER_ALREADY_CONNECTED_ERROR}"
strategy.session[REDIRECT_FIELD_NAME] = url
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
# at this point everything is correct and we can create the SlackUserIdentity
# be sure to clear any pre-existing sessions, in case the user previously enecountered errors we want
# to be sure to clear these so they do not see them again
strategy.session.flush()
slack_user_identity, _ = SlackUserIdentity.objects.get_or_create(
slack_id=slack_user_id,
slack_team_identity=slack_team_identity,
defaults={
"cached_slack_email": response["user"]["email"],
},
)
write_chatops_insight_log(
author=user,
event_name=ChatOpsEvent.USER_LINKED,
chatops_type=ChatOpsTypePlug.SLACK.value,
linked_user=user.username,
linked_user_id=user.public_primary_key,
)
user.slack_user_identity = slack_user_identity
user.save(update_fields=["slack_user_identity"])
slack_user_identity.update_profile_info()
def populate_slack_identities(response, backend, user, organization, **kwargs):
from apps.slack.models import SlackTeamIdentity
# Continue pipeline step only if it was installation
if backend.name != "slack-install-free":
return
if organization.slack_team_identity is not None:
# means that organization already has Slack integration
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
slack_team_id = response["team"]["id"]
if settings.FEATURE_MULTIREGION_ENABLED and not check_slack_installation_possible(
str(organization.uuid), slack_team_id, settings.ONCALL_BACKEND_REGION
):
raise InstallMultiRegionSlackException
slack_team_identity, is_slack_team_identity_created = SlackTeamIdentity.objects.get_or_create(
slack_id=slack_team_id,
)
# update slack oauth fields by data from response
slack_team_identity.update_oauth_fields(user, organization, response)
if settings.FEATURE_MULTIREGION_ENABLED:
create_slack_connector(str(organization.uuid), slack_team_id, settings.ONCALL_BACKEND_REGION)
populate_slack_channels_for_team.apply_async((slack_team_identity.pk,))
user.slack_user_identity.update_profile_info()
# todo slack: do we need update info for all existing slack users in slack team?
# populate_slack_user_identities.apply_async((organization.pk,))
populate_slack_usergroups_for_team.apply_async((slack_team_identity.pk,), countdown=10)
def delete_slack_auth_token(strategy, *args, **kwargs):
strategy.request.auth.delete()