Add CloudUsersView and CloudUserView

This commit is contained in:
Innokentii Konstantinov 2022-06-04 16:49:10 +04:00
parent 5e494531eb
commit 75f319fb5d
9 changed files with 125 additions and 20 deletions

View file

@ -4,11 +4,13 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from apps.auth_token.auth import PluginAuthentication
from apps.base.utils import live_settings
FEATURE_SLACK = "slack"
FEATURE_TELEGRAM = "telegram"
FEATURE_LIVE_SETTINGS = "live_settings"
MOBILE_APP_PUSH_NOTIFICATIONS = "mobile_app"
FEATURE_GRAFANA_CLOUD_NOTIFICATIONS = "grafana_cloud_notifications"
class FeaturesAPIView(APIView):
@ -34,6 +36,9 @@ class FeaturesAPIView(APIView):
if settings.FEATURE_LIVE_SETTINGS_ENABLED:
enabled_features.append(FEATURE_LIVE_SETTINGS)
if live_settings.GRAFANA_CLOUD_NOTIFICATIONS_ENABLED:
enabled_features.append(FEATURE_GRAFANA_CLOUD_NOTIFICATIONS)
if settings.MOBILE_APP_PUSH_NOTIFICATIONS_ENABLED:
DynamicSetting = apps.get_model("base", "DynamicSetting")
mobile_app_settings = DynamicSetting.objects.get_or_create(

View file

@ -1,7 +1,10 @@
import json
import re
from urllib.parse import urljoin
import requests.exceptions
from django.apps import apps
from django.conf import settings
from python_http_client import UnauthorizedError
from sendgrid import SendGridAPIClient
from telegram import Bot
@ -94,6 +97,20 @@ class LiveSettingValidator:
except Exception as e:
return f"Telegram error: {str(e)}"
@classmethod
def _check_grafana_cloud_oncall_token(cls, grafan_oncall_token):
try:
info_url = urljoin(settings.GRAFANA_CLOUD_ONCALL_API_URL, "api/v1/info/")
r = requests.get(info_url, headers={"AUTHORIZATION": grafan_oncall_token}, timeout=5)
if r.status_code == 200:
return
elif r.status_code == 403:
return f"Invalid token."
else:
return f"Non-200 HTTP code. Got {r.status_code}"
except requests.exceptions.RequestException as e:
return f"Error {str(e)}"
@staticmethod
def _is_email_valid(email):
return re.match(r"^[^@]+@[^@]+\.[^@]+$", email)

View file

@ -1 +1,6 @@
CLOUD_URL = "https://a-prod-us-central-0.grafana.net/"
CLOUD_NOT_SYNCED = 0
CLOUD_SYNCED_USER_NOT_FOUND = 1
CLOUD_SYNCED_PHONE_NOT_VERIFIED = 2
CLOUD_SYNCED_PHONE_VERIFIED = 3

View file

@ -1,4 +1,4 @@
from .cloud_organization_connector import CloudOrganizationConnector # noqa: F401
from .cloud_users import CloudUserIdentity # noqa: F401
from .cloud_user_identity import CloudUserIdentity # noqa: F401
from .heartbeat import CloudHeartbeat # noqa: F401
from .oss_installation import OssInstallation # noqa: F401

View file

@ -6,7 +6,7 @@ from django.db import models
from apps.base.utils import live_settings
from apps.oss_installation.constants import CLOUD_URL
from apps.oss_installation.models.cloud_users import CloudUserIdentity
from apps.oss_installation.models.cloud_user_identity import CloudUserIdentity
from apps.user_management.models import User
logger = logging.getLogger(__name__)
@ -59,7 +59,7 @@ class CloudOrganizationConnector(models.Model):
users_url = urljoin(CLOUD_URL, "api/v1/users")
existing_cloud_identities = list(CloudUserIdentity.objects.filter(organization=self.organization))
existing_cloud_ids = list(map(lambda u: u.cloud_id, existing_cloud_identities))
existing_cloud_ids = list(map(lambda identity: identity.cloud_id, existing_cloud_identities))
fetch_next_page = True
page = 1
@ -102,7 +102,6 @@ class CloudOrganizationConnector(models.Model):
i.email = cloud_users_identities_to_update[i.cloud_id]["email"]
i.phone_number_verified = cloud_users_identities_to_update[i.cloud_id]["is_phone_number_verified"]
# TODO: Grafana CN: check if data validation needed.
CloudUserIdentity.objects.bulk_create(cloud_users_identities_to_create, batch_size=1000)
CloudUserIdentity.objects.bulk_update(
existing_cloud_identities, ["email", "phone_number_verified"], batch_size=1000

View file

@ -1,8 +1,20 @@
from django.urls import path
from common.api_helpers.optional_slash_router import optional_slash_path
from .views import CloudHeartbeatStatusView, CloudUsersView
from .views.cloud_user import CloudUserView
urlpatterns = [
optional_slash_path("cloud_heartbeat_status", CloudHeartbeatStatusView.as_view(), name="cloud_heartbeat_status"),
optional_slash_path("cloud_users", CloudUsersView.as_view(), name="cloud_users"),
optional_slash_path("cloud_users", CloudUsersView.as_view(), name="cloud-users-list"),
path(
"cloud_users/<str:pk>",
CloudUserView.as_view(
{
"get": "retrieve",
}
),
name="cloud-user-detail",
),
]

View file

@ -0,0 +1,61 @@
from urllib.parse import urljoin
from rest_framework import mixins, serializers, viewsets
from rest_framework.permissions import IsAuthenticated
import apps.oss_installation.constants as cloud_constants
from apps.api.permissions import ActionPermission, IsOwnerOrAdmin
from apps.auth_token.auth import PluginAuthentication
from apps.oss_installation.models import CloudOrganizationConnector, CloudUserIdentity
from apps.user_management.models import User
from common.api_helpers.mixins import PublicPrimaryKeyMixin
class CloudUserSerializer(serializers.ModelSerializer):
cloud_data = serializers.SerializerMethodField()
class Meta:
model = User
fields = ["sync_data"]
def get_cloud_data(self, obj):
link = None
status = cloud_constants.CLOUD_NOT_SYNCED
connector = CloudOrganizationConnector.objects.filter(
organization=self.context["request"].auth.organization
).first()
if connector is not None:
cloud_user_identity = CloudUserIdentity.objects.filter(email=obj.email).first()
if cloud_user_identity is None:
status = cloud_constants.CLOUD_SYNCED_USER_NOT_FOUND
link = connector.cloud_url
elif not cloud_user_identity.phone_number_verified:
status = cloud_constants.CLOUD_SYNCED_USER_NOT_FOUND
link = urljoin(
connector.cloud_url, f"a/grafana-oncall-app/?page=users&p=1&id={cloud_user_identity.cloud_id}"
)
else:
status = cloud_constants.CLOUD_SYNCED_PHONE_VERIFIED
link = urljoin(
connector.cloud_url, f"a/grafana-oncall-app/?page=users&p=1&id={cloud_user_identity.cloud_id}"
)
cloud_data = {"status": status, "link": link}
return cloud_data
class CloudUserView(
PublicPrimaryKeyMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet,
):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
action_object_permissions = {
IsOwnerOrAdmin: ("retrieve",),
}
serializer_class = CloudUserSerializer
def get_queryset(self):
queryset = User.objects.filter(organization=self.request.user.organization)
return queryset

View file

@ -3,6 +3,8 @@ from urllib.parse import urljoin
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
import apps.oss_installation.constants as cloud_constants
from apps.api.permissions import IsAdmin
from apps.auth_token.auth import PluginAuthentication
from apps.oss_installation.models import CloudOrganizationConnector, CloudUserIdentity
from apps.user_management.models import User
@ -11,8 +13,7 @@ from common.api_helpers.paginators import HundredPageSizePaginator
class CloudUsersView(HundredPageSizePaginator, APIView):
authentication_classes = (PluginAuthentication,)
# TODO: Grafana CN - permissions, ratelimit
permission_classes = (IsAuthenticated,)
permission_classes = (IsAuthenticated, IsAdmin)
def get(self, request):
queryset = User.objects.filter(organization=self.request.user.organization)
@ -31,23 +32,28 @@ class CloudUsersView(HundredPageSizePaginator, APIView):
response = []
connector = CloudOrganizationConnector.objects.first()
for user in results:
cloud_identity = cloud_identities.get(user.email, None)
link = None
status = 0
if cloud_identity:
status = 1
is_phone_verified = cloud_identity.phone_number_verified
if is_phone_verified:
status = 2
link = urljoin(
connector.cloud_url, f"a/grafana-oncall-app/?page=users&p=1&id={cloud_identity.cloud_id}"
)
status = cloud_constants.CLOUD_NOT_SYNCED
if connector is not None:
status = cloud_constants.CLOUD_SYNCED_USER_NOT_FOUND
cloud_identity = cloud_identities.get(user.email, None)
if cloud_identity:
status = cloud_constants.CLOUD_SYNCED_PHONE_NOT_VERIFIED
is_phone_verified = cloud_identity.phone_number_verified
if is_phone_verified:
status = cloud_constants.CLOUD_SYNCED_PHONE_VERIFIED
link = urljoin(
connector.cloud_url, f"a/grafana-oncall-app/?page=users&p=1&id={cloud_identity.cloud_id}"
)
# TODO: Grafana CN - decide if emails is needed. If yes - don't forget to check that they mustn't be shown to users
response.append(
{"id": user.public_primary_key, "username": user.username, "cloud_sync_status": status, "link": link}
{
"id": user.public_primary_key,
"email": user.email,
"username": user.username,
"cloud_data": {"status": status, "link": link},
}
)
return self.get_paginated_response(response)