oncall-engine/engine/apps/grafana_plugin/helpers/gcom.py
Vadim Stepanov c921674471
Improve plugin authentication (#1995)
# What this PR does
Handle different failing authentication scenarios (e.g. when token is
invalid or instance context is not a valid JSON) so endpoints return
appropriate response code (401 instead of 500).

## Which issue(s) this PR fixes
Related to https://github.com/grafana/oncall-private/issues/1633

## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-05-23 16:13:25 +00:00

130 lines
4.8 KiB
Python

import logging
from typing import Optional, Tuple
from django.apps import apps
from django.conf import settings
from django.utils import timezone
from apps.auth_token.exceptions import InvalidToken
from apps.auth_token.models import PluginAuthToken
from apps.grafana_plugin.helpers import GcomAPIClient
from apps.user_management.models import Organization
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
GCOM_TOKEN_CHECK_PERIOD = timezone.timedelta(minutes=60)
class GcomToken:
def __init__(self, organization):
self.organization = organization
def check_gcom_permission(token_string: str, context) -> GcomToken:
"""
Verify that request from plugin is valid. Check it and synchronize the organization details
with gcom every GCOM_TOKEN_CHECK_PERIOD.
"""
stack_id = context["stack_id"]
org_id = context["org_id"]
organization = Organization.objects.filter(stack_id=stack_id, org_id=org_id).first()
if (
organization
and organization.gcom_token == token_string
and organization.gcom_token_org_last_time_synced
and timezone.now() - organization.gcom_token_org_last_time_synced < GCOM_TOKEN_CHECK_PERIOD
):
logger.debug(f"Allow request without calling gcom api for org={org_id}, stack_id={stack_id}")
return GcomToken(organization)
logger.debug(f"Start authenticate by making request to gcom api for org={org_id}, stack_id={stack_id}")
client = GcomAPIClient(token_string)
instance_info = client.get_instance_info(stack_id)
if not instance_info or str(instance_info["orgId"]) != org_id:
raise InvalidToken
if not organization:
DynamicSetting = apps.get_model("base", "DynamicSetting")
allow_signup = DynamicSetting.objects.get_or_create(
name="allow_plugin_organization_signup", defaults={"boolean_value": True}
)[0].boolean_value
if allow_signup:
organization = Organization.objects.create(
stack_id=str(instance_info["id"]),
stack_slug=instance_info["slug"],
grafana_url=instance_info["url"],
org_id=str(instance_info["orgId"]),
org_slug=instance_info["orgSlug"],
org_title=instance_info["orgName"],
region_slug=instance_info["regionSlug"],
cluster_slug=instance_info["clusterSlug"],
gcom_token=token_string,
gcom_token_org_last_time_synced=timezone.now(),
)
else:
organization.stack_slug = instance_info["slug"]
organization.org_slug = instance_info["orgSlug"]
organization.org_title = instance_info["orgName"]
organization.region_slug = instance_info["regionSlug"]
organization.grafana_url = instance_info["url"]
organization.cluster_slug = instance_info["clusterSlug"]
organization.gcom_token = token_string
organization.gcom_token_org_last_time_synced = timezone.now()
organization.save(
update_fields=[
"stack_slug",
"org_slug",
"org_title",
"region_slug",
"grafana_url",
"gcom_token",
"gcom_token_org_last_time_synced",
"cluster_slug",
]
)
logger.debug(f"Finish authenticate by making request to gcom api for org={org_id}, stack_id={stack_id}")
return GcomToken(organization)
def check_token(token_string: str, context: dict) -> GcomToken | PluginAuthToken:
token_parts = token_string.split(":")
if len(token_parts) > 1 and token_parts[0] == "gcom":
return check_gcom_permission(token_parts[1], context)
else:
return PluginAuthToken.validate_token_string(token_string, context=context)
def get_instance_ids(query: str) -> Tuple[Optional[set], bool]:
if not settings.GRAFANA_COM_API_TOKEN or settings.LICENSE != settings.CLOUD_LICENSE_NAME:
return None, False
client = GcomAPIClient(settings.GRAFANA_COM_API_TOKEN)
instances, status = client.get_instances(query)
if not instances:
return None, True
ids = set(i["id"] for i in instances["items"])
return ids, True
def get_active_instance_ids() -> Tuple[Optional[set], bool]:
return get_instance_ids(GcomAPIClient.ACTIVE_INSTANCE_QUERY)
def get_deleted_instance_ids() -> Tuple[Optional[set], bool]:
return get_instance_ids(GcomAPIClient.DELETED_INSTANCE_QUERY)
def get_stack_regions() -> Tuple[Optional[set], bool]:
if not settings.GRAFANA_COM_API_TOKEN or settings.LICENSE != settings.CLOUD_LICENSE_NAME:
return None, False
client = GcomAPIClient(settings.GRAFANA_COM_API_TOKEN)
regions, status = client.get_stack_regions()
if not regions or "items" not in regions:
return None, True
return regions["items"], True