oncall-engine/engine/apps/social_auth/pipeline/google.py
Joey Orlando bfcc0b9f29
update URLs constructed by the backend to support IRM plugin (#5137)
# What this PR does

Introduces a new class,
`apps.grafana_plugin.ui_url_builder.UIURLBuilder`, which is responsible
for... building UI URLs (😄). The class mainly does two things:
- it will decide if the URL should point to `grafana-oncall-app` or
`grafana-irm-app` based on the value of
`organization.is_grafana_irm_enabled` (**NOTE**: this value isn't yet
being set + defaults to `False`; logic for setting this value will be
done in a subsequent PR)
- Adds `enum`s, `OnCallPage` and `IncidentPage` to DRYify hardcoded UI
URLs (in case we decide to change these slightly in the near future)

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
2024-10-09 08:55:10 -04:00

89 lines
3.2 KiB
Python

import logging
import typing
import requests
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponse
from rest_framework import status
from social_core.backends.base import BaseAuth
from apps.google.utils import user_granted_all_required_scopes
from apps.grafana_plugin.ui_url_builder import UIURLBuilder
from apps.social_auth.exceptions import GOOGLE_AUTH_MISSING_GRANTED_SCOPE_ERROR
from apps.social_auth.types import GoogleOauth2Response
from apps.user_management.models import Organization, User
logger = logging.getLogger(__name__)
def connect_user_to_google(
strategy,
response: GoogleOauth2Response,
user: User,
organization: Organization,
*args,
**kwargs,
):
granted_scopes = response.get("scope", "")
if not user_granted_all_required_scopes(granted_scopes):
logger.warning(
f"User {user.pk} did not grant all required scopes, redirecting w/ error message "
f"granted_scopes={granted_scopes}"
)
strategy.session[REDIRECT_FIELD_NAME] = UIURLBuilder(organization).user_profile(
f"?google_error={GOOGLE_AUTH_MISSING_GRANTED_SCOPE_ERROR}"
)
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
# at this point everything is correct and we can persist the Google OAuth2 token + generate any other relevant
# config for the user
#
# 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()
user.save_google_oauth2_settings(response)
def disconnect_user_google_oauth2_settings(backend: typing.Type[BaseAuth], user: User, *args, **kwargs):
"""
Don't use `google_oauth2_user.access_token` when revoking token, use `refresh_token` instead. If we use
the access token, we may get an HTTP 400 from Google because the token may be invalid or revoked.
https://stackoverflow.com/a/18578660/3902555
"""
user_pk = user.pk
google_oauth2_user = user.google_oauth2_user
logger.info(f"Disconnecting user {user_pk} from Google OAuth2")
try:
backend.revoke_token(google_oauth2_user.refresh_token, google_oauth2_user.google_user_id)
except requests.exceptions.HTTPError as e:
response = e.response
logger.error(f"There was an HTTP error when trying to revoke Google OAuth2 token for user={user_pk}")
if response.status_code == 400:
error_details = response.json()
error_code = error_details["error"]
error_description = error_details["error_description"]
logger.error(
f"There was an HTTP 400 error when trying to revoke Google OAuth2 token for user={user_pk} "
f"error_code={error_code} error_description={error_description}"
)
error_codes_to_ignore = ["invalid_token"]
if error_code not in error_codes_to_ignore:
raise e
else:
logger.info(f"Google OAuth2 token for user {user_pk} is already invalid or revoked, ignoring error")
user.reset_google_oauth2_settings()
logger.info(f"Successfully disconnected user {user.pk} from Google OAuth2")