2022-06-03 08:09:47 -06:00
|
|
|
import json
|
|
|
|
|
import logging
|
2024-04-02 14:59:03 -04:00
|
|
|
import typing
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
from django.contrib.auth.models import AnonymousUser
|
2024-01-12 15:11:22 +00:00
|
|
|
from drf_spectacular.extensions import OpenApiAuthenticationExtension
|
2022-06-03 08:09:47 -06:00
|
|
|
from rest_framework import exceptions
|
|
|
|
|
from rest_framework.authentication import BaseAuthentication, get_authorization_header
|
|
|
|
|
from rest_framework.request import Request
|
|
|
|
|
|
2023-11-23 09:42:27 -07:00
|
|
|
from apps.api.permissions import GrafanaAPIPermission, LegacyAccessControlRole, RBACPermission, user_is_authorized
|
2022-06-03 08:09:47 -06:00
|
|
|
from apps.grafana_plugin.helpers.gcom import check_token
|
2023-01-05 12:42:55 +08:00
|
|
|
from apps.user_management.exceptions import OrganizationDeletedException, OrganizationMovedException
|
2022-06-03 08:09:47 -06:00
|
|
|
from apps.user_management.models import User
|
|
|
|
|
from apps.user_management.models.organization import Organization
|
2023-11-23 09:42:27 -07:00
|
|
|
from settings.base import SELF_HOSTED_SETTINGS
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
from .constants import GOOGLE_OAUTH2_AUTH_TOKEN_NAME, SCHEDULE_EXPORT_TOKEN_NAME, SLACK_AUTH_TOKEN_NAME
|
2022-06-03 08:09:47 -06:00
|
|
|
from .exceptions import InvalidToken
|
2023-11-23 09:42:27 -07:00
|
|
|
from .grafana.grafana_auth_token import get_service_account_token_permissions
|
2024-04-02 14:59:03 -04:00
|
|
|
from .models import (
|
|
|
|
|
ApiAuthToken,
|
|
|
|
|
GoogleOAuth2Token,
|
|
|
|
|
IntegrationBacksyncAuthToken,
|
|
|
|
|
PluginAuthToken,
|
|
|
|
|
ScheduleExportAuthToken,
|
|
|
|
|
SlackAuthToken,
|
|
|
|
|
UserScheduleExportAuthToken,
|
|
|
|
|
)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
T = typing.TypeVar("T")
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2024-03-18 10:16:54 +01:00
|
|
|
class ServerUser(AnonymousUser):
|
|
|
|
|
@property
|
|
|
|
|
def is_authenticated(self):
|
|
|
|
|
# Always return True. This is a way to tell if
|
|
|
|
|
# the user has been authenticated in permissions
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
class ApiTokenAuthentication(BaseAuthentication):
|
|
|
|
|
model = ApiAuthToken
|
|
|
|
|
|
|
|
|
|
def authenticate(self, request):
|
|
|
|
|
auth = get_authorization_header(request).decode("utf-8")
|
|
|
|
|
user, auth_token = self.authenticate_credentials(auth)
|
|
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
if not user_is_authorized(user, [RBACPermission.Permissions.API_KEYS_WRITE]):
|
2022-06-03 08:09:47 -06:00
|
|
|
raise exceptions.AuthenticationFailed(
|
|
|
|
|
"Only users with Admin permissions are allowed to perform this action."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return user, auth_token
|
|
|
|
|
|
|
|
|
|
def authenticate_credentials(self, token):
|
|
|
|
|
"""
|
|
|
|
|
Due to the random nature of hashing a value, this must inspect
|
|
|
|
|
each auth_token individually to find the correct one.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
auth_token = self.model.validate_token_string(token)
|
|
|
|
|
except InvalidToken:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token.")
|
2022-10-20 15:04:58 -06:00
|
|
|
|
2022-10-28 15:45:51 -06:00
|
|
|
if auth_token.organization.is_moved:
|
2022-10-20 15:04:58 -06:00
|
|
|
raise OrganizationMovedException(auth_token.organization)
|
2023-02-24 04:45:21 -07:00
|
|
|
if auth_token.organization.deleted_at:
|
|
|
|
|
raise OrganizationDeletedException(auth_token.organization)
|
2022-10-20 15:04:58 -06:00
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
return auth_token.user, auth_token
|
|
|
|
|
|
|
|
|
|
|
2023-08-22 11:17:26 -06:00
|
|
|
class BasePluginAuthentication(BaseAuthentication):
|
|
|
|
|
"""
|
|
|
|
|
Authentication used by grafana-plugin app where we tolerate user not being set yet due to being in
|
|
|
|
|
a state of initialization, Only validates that the plugin should be talking to the backend. Outside of
|
|
|
|
|
this app PluginAuthentication should be used since it also checks the user.
|
|
|
|
|
"""
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
def authenticate_header(self, request):
|
|
|
|
|
# Check parent's method comments
|
|
|
|
|
return "Bearer"
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
def authenticate(self, request: Request) -> typing.Tuple[User, PluginAuthToken]:
|
2022-06-03 08:09:47 -06:00
|
|
|
token_string = get_authorization_header(request).decode()
|
|
|
|
|
|
|
|
|
|
if not token_string:
|
|
|
|
|
raise exceptions.AuthenticationFailed("No token provided")
|
|
|
|
|
|
|
|
|
|
return self.authenticate_credentials(token_string, request)
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
def authenticate_credentials(self, token_string: str, request: Request) -> typing.Tuple[User, PluginAuthToken]:
|
2022-06-03 08:09:47 -06:00
|
|
|
context_string = request.headers.get("X-Instance-Context")
|
|
|
|
|
if not context_string:
|
|
|
|
|
raise exceptions.AuthenticationFailed("No instance context provided.")
|
|
|
|
|
|
2023-05-23 17:13:25 +01:00
|
|
|
try:
|
|
|
|
|
context = dict(json.loads(context_string))
|
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
raise exceptions.AuthenticationFailed("Instance context must be JSON dict.")
|
|
|
|
|
|
|
|
|
|
if "stack_id" not in context or "org_id" not in context:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid instance context.")
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
try:
|
|
|
|
|
auth_token = check_token(token_string, context=context)
|
|
|
|
|
if not auth_token.organization:
|
|
|
|
|
raise exceptions.AuthenticationFailed("No organization associated with token.")
|
|
|
|
|
except InvalidToken:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token.")
|
|
|
|
|
|
|
|
|
|
user = self._get_user(request, auth_token.organization)
|
|
|
|
|
return user, auth_token
|
|
|
|
|
|
2023-08-22 11:17:26 -06:00
|
|
|
@staticmethod
|
|
|
|
|
def _get_user(request: Request, organization: Organization) -> User:
|
|
|
|
|
try:
|
|
|
|
|
context = dict(json.loads(request.headers.get("X-Grafana-Context")))
|
|
|
|
|
except (ValueError, TypeError):
|
2024-01-23 15:59:33 -07:00
|
|
|
logger.info("auth request user not found - missing valid X-Grafana-Context")
|
2023-08-22 11:17:26 -06:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
if "UserId" not in context and "UserID" not in context:
|
2024-01-23 15:59:33 -07:00
|
|
|
logger.info("auth request user not found - X-Grafana-Context missing UserID")
|
2023-08-22 11:17:26 -06:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
user_id = context["UserId"]
|
|
|
|
|
except KeyError:
|
|
|
|
|
user_id = context["UserID"]
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
return organization.users.get(user_id=user_id)
|
|
|
|
|
except User.DoesNotExist:
|
2024-01-23 15:59:33 -07:00
|
|
|
logger.info(f"auth request user not found - user_id={user_id}")
|
2023-08-22 11:17:26 -06:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PluginAuthentication(BasePluginAuthentication):
|
2022-06-03 08:09:47 -06:00
|
|
|
@staticmethod
|
|
|
|
|
def _get_user(request: Request, organization: Organization) -> User:
|
2023-05-23 17:13:25 +01:00
|
|
|
try:
|
|
|
|
|
context = dict(json.loads(request.headers.get("X-Grafana-Context")))
|
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
raise exceptions.AuthenticationFailed("Grafana context must be JSON dict.")
|
|
|
|
|
|
|
|
|
|
if "UserId" not in context and "UserID" not in context:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid Grafana context.")
|
|
|
|
|
|
2022-08-12 09:57:54 -06:00
|
|
|
try:
|
|
|
|
|
user_id = context["UserId"]
|
|
|
|
|
except KeyError:
|
|
|
|
|
user_id = context["UserID"]
|
2023-05-23 17:13:25 +01:00
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
try:
|
|
|
|
|
return organization.users.get(user_id=user_id)
|
|
|
|
|
except User.DoesNotExist:
|
|
|
|
|
logger.debug(f"Could not get user from grafana request. Context {context}")
|
|
|
|
|
raise exceptions.AuthenticationFailed("Non-existent or anonymous user.")
|
|
|
|
|
|
|
|
|
|
|
2024-01-12 15:11:22 +00:00
|
|
|
class PluginAuthenticationSchema(OpenApiAuthenticationExtension):
|
|
|
|
|
target_class = PluginAuthentication
|
|
|
|
|
name = "PluginAuthentication"
|
|
|
|
|
|
|
|
|
|
def get_security_definition(self, auto_schema):
|
|
|
|
|
return {
|
|
|
|
|
"type": "apiKey",
|
|
|
|
|
"in": "header",
|
|
|
|
|
"name": "Authorization",
|
|
|
|
|
"description": (
|
|
|
|
|
"Additional X-Instance-Context and X-Grafana-Context headers must be set. "
|
|
|
|
|
"THIS WILL NOT WORK IN SWAGGER UI."
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
class GrafanaIncidentUser(AnonymousUser):
|
|
|
|
|
@property
|
|
|
|
|
def is_authenticated(self):
|
|
|
|
|
# Always return True. This is a way to tell if
|
|
|
|
|
# the user has been authenticated in permissions
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GrafanaIncidentStaticKeyAuth(BaseAuthentication):
|
|
|
|
|
def authenticate_header(self, request): # noqa
|
|
|
|
|
# Check parent's method comments
|
|
|
|
|
return "Bearer"
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
def authenticate(self, request: Request) -> typing.Tuple[GrafanaIncidentUser, None]:
|
2022-06-03 08:09:47 -06:00
|
|
|
token_string = get_authorization_header(request).decode()
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
not token_string == settings.GRAFANA_INCIDENT_STATIC_API_KEY
|
|
|
|
|
or settings.GRAFANA_INCIDENT_STATIC_API_KEY is None
|
|
|
|
|
):
|
|
|
|
|
raise exceptions.AuthenticationFailed("Wrong token")
|
|
|
|
|
|
|
|
|
|
if not token_string:
|
|
|
|
|
raise exceptions.AuthenticationFailed("No token provided")
|
|
|
|
|
|
|
|
|
|
return self.authenticate_credentials(token_string, request)
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
def authenticate_credentials(self, token_string: str, request: Request) -> typing.Tuple[GrafanaIncidentUser, None]:
|
2022-06-03 08:09:47 -06:00
|
|
|
try:
|
|
|
|
|
user = GrafanaIncidentUser()
|
|
|
|
|
except InvalidToken:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token.")
|
|
|
|
|
|
|
|
|
|
return user, None
|
|
|
|
|
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
class _SocialAuthTokenAuthentication(BaseAuthentication, typing.Generic[T]):
|
|
|
|
|
def authenticate(self, request) -> typing.Optional[typing.Tuple[User, T]]:
|
|
|
|
|
"""
|
|
|
|
|
If you don't return `None`, the authenticate will raise an `APIException`, so the next authentication class
|
|
|
|
|
will not be called.
|
|
|
|
|
https://stackoverflow.com/a/61623607/3902555
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
This is useful for the social_auth views where we want to use multiple authentication classes
|
|
|
|
|
for the same view.
|
|
|
|
|
"""
|
|
|
|
|
auth = request.query_params.get(self.token_query_param_name)
|
2022-06-03 08:09:47 -06:00
|
|
|
if not auth:
|
2024-04-02 14:59:03 -04:00
|
|
|
return None
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
try:
|
2024-04-02 14:59:03 -04:00
|
|
|
auth_token = self.model.validate_token_string(auth)
|
|
|
|
|
return auth_token.user, auth_token
|
2022-06-03 08:09:47 -06:00
|
|
|
except InvalidToken:
|
2024-04-02 14:59:03 -04:00
|
|
|
return None
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
|
|
|
|
|
class SlackTokenAuthentication(_SocialAuthTokenAuthentication[SlackAuthToken]):
|
|
|
|
|
token_query_param_name = SLACK_AUTH_TOKEN_NAME
|
|
|
|
|
model = SlackAuthToken
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GoogleTokenAuthentication(_SocialAuthTokenAuthentication[GoogleOAuth2Token]):
|
|
|
|
|
token_query_param_name = GOOGLE_OAUTH2_AUTH_TOKEN_NAME
|
|
|
|
|
model = GoogleOAuth2Token
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ScheduleExportAuthentication(BaseAuthentication):
|
|
|
|
|
model = ScheduleExportAuthToken
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
def authenticate(self, request) -> typing.Tuple[User, ScheduleExportAuthToken]:
|
2022-06-03 08:09:47 -06:00
|
|
|
auth = request.query_params.get(SCHEDULE_EXPORT_TOKEN_NAME)
|
|
|
|
|
public_primary_key = request.parser_context.get("kwargs", {}).get("pk")
|
|
|
|
|
if not auth:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token.")
|
|
|
|
|
|
|
|
|
|
auth_token = self.authenticate_credentials(auth, public_primary_key)
|
|
|
|
|
return auth_token
|
|
|
|
|
|
|
|
|
|
def authenticate_credentials(
|
|
|
|
|
self, token_string: str, public_primary_key: str
|
2024-04-02 14:59:03 -04:00
|
|
|
) -> typing.Tuple[User, ScheduleExportAuthToken]:
|
2022-06-03 08:09:47 -06:00
|
|
|
try:
|
|
|
|
|
auth_token = self.model.validate_token_string(token_string)
|
|
|
|
|
except InvalidToken:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token.")
|
|
|
|
|
|
2022-10-28 15:45:51 -06:00
|
|
|
if auth_token.organization.is_moved:
|
2022-10-20 15:04:58 -06:00
|
|
|
raise OrganizationMovedException(auth_token.organization)
|
2023-02-24 04:45:21 -07:00
|
|
|
if auth_token.organization.deleted_at:
|
|
|
|
|
raise OrganizationDeletedException(auth_token.organization)
|
2022-10-20 15:04:58 -06:00
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
if auth_token.schedule.public_primary_key != public_primary_key:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid schedule export token for schedule")
|
|
|
|
|
|
|
|
|
|
if not auth_token.active:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Export token is deactivated")
|
|
|
|
|
|
|
|
|
|
return auth_token.user, auth_token
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserScheduleExportAuthentication(BaseAuthentication):
|
|
|
|
|
model = UserScheduleExportAuthToken
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
def authenticate(self, request) -> typing.Tuple[User, UserScheduleExportAuthToken]:
|
2022-06-03 08:09:47 -06:00
|
|
|
auth = request.query_params.get(SCHEDULE_EXPORT_TOKEN_NAME)
|
|
|
|
|
public_primary_key = request.parser_context.get("kwargs", {}).get("pk")
|
|
|
|
|
|
|
|
|
|
if not auth:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token.")
|
|
|
|
|
|
|
|
|
|
auth_token = self.authenticate_credentials(auth, public_primary_key)
|
|
|
|
|
return auth_token
|
|
|
|
|
|
|
|
|
|
def authenticate_credentials(
|
|
|
|
|
self, token_string: str, public_primary_key: str
|
2024-04-02 14:59:03 -04:00
|
|
|
) -> typing.Tuple[User, UserScheduleExportAuthToken]:
|
2022-06-03 08:09:47 -06:00
|
|
|
try:
|
|
|
|
|
auth_token = self.model.validate_token_string(token_string)
|
|
|
|
|
except InvalidToken:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token")
|
|
|
|
|
|
2022-10-28 15:45:51 -06:00
|
|
|
if auth_token.organization.is_moved:
|
2022-10-20 15:04:58 -06:00
|
|
|
raise OrganizationMovedException(auth_token.organization)
|
2023-02-24 04:45:21 -07:00
|
|
|
if auth_token.organization.deleted_at:
|
|
|
|
|
raise OrganizationDeletedException(auth_token.organization)
|
2022-10-20 15:04:58 -06:00
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
if auth_token.user.public_primary_key != public_primary_key:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid schedule export token for user")
|
|
|
|
|
|
|
|
|
|
if not auth_token.active:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Export token is deactivated")
|
|
|
|
|
|
|
|
|
|
return auth_token.user, auth_token
|
2023-11-23 09:42:27 -07:00
|
|
|
|
|
|
|
|
|
2023-11-28 08:56:29 -07:00
|
|
|
X_GRAFANA_INSTANCE_ID = "X-Grafana-Instance-ID"
|
2023-11-23 09:42:27 -07:00
|
|
|
GRAFANA_SA_PREFIX = "glsa_"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GrafanaServiceAccountAuthentication(BaseAuthentication):
|
|
|
|
|
def authenticate(self, request):
|
|
|
|
|
auth = get_authorization_header(request).decode("utf-8")
|
|
|
|
|
if not auth:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token.")
|
|
|
|
|
if not auth.startswith(GRAFANA_SA_PREFIX):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
organization = self.get_organization(request)
|
|
|
|
|
if not organization:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid organization.")
|
|
|
|
|
if organization.is_moved:
|
|
|
|
|
raise OrganizationMovedException(organization)
|
|
|
|
|
if organization.deleted_at:
|
|
|
|
|
raise OrganizationDeletedException(organization)
|
|
|
|
|
|
|
|
|
|
return self.authenticate_credentials(organization, auth)
|
|
|
|
|
|
|
|
|
|
def get_organization(self, request):
|
|
|
|
|
if settings.LICENSE == settings.CLOUD_LICENSE_NAME:
|
2023-11-28 08:56:29 -07:00
|
|
|
instance_id = request.headers.get(X_GRAFANA_INSTANCE_ID)
|
|
|
|
|
if not instance_id:
|
|
|
|
|
raise exceptions.AuthenticationFailed(f"Missing {X_GRAFANA_INSTANCE_ID}")
|
|
|
|
|
return Organization.objects.filter(stack_id=instance_id).first()
|
|
|
|
|
else:
|
|
|
|
|
org_slug = SELF_HOSTED_SETTINGS["ORG_SLUG"]
|
|
|
|
|
instance_slug = SELF_HOSTED_SETTINGS["STACK_SLUG"]
|
|
|
|
|
return Organization.objects.filter(org_slug=org_slug, stack_slug=instance_slug).first()
|
2023-11-23 09:42:27 -07:00
|
|
|
|
|
|
|
|
def authenticate_credentials(self, organization, token):
|
|
|
|
|
permissions = get_service_account_token_permissions(organization, token)
|
|
|
|
|
if not permissions:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token.")
|
|
|
|
|
|
|
|
|
|
role = LegacyAccessControlRole.NONE
|
|
|
|
|
if not organization.is_rbac_permissions_enabled:
|
|
|
|
|
role = self.determine_role_from_permissions(permissions)
|
|
|
|
|
|
|
|
|
|
user = User(
|
|
|
|
|
organization_id=organization.pk,
|
|
|
|
|
name="Grafana Service Account",
|
|
|
|
|
username="grafana_service_account",
|
|
|
|
|
role=role,
|
|
|
|
|
permissions=[GrafanaAPIPermission(action=key) for key, _ in permissions.items()],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
auth_token = ApiAuthToken(organization=organization, user=user, name="Grafana Service Account")
|
|
|
|
|
|
|
|
|
|
return user, auth_token
|
|
|
|
|
|
|
|
|
|
# Using default permissions as proxies for roles since we cannot explicitly get role from the service account token
|
|
|
|
|
def determine_role_from_permissions(self, permissions):
|
|
|
|
|
if "plugins:write" in permissions:
|
|
|
|
|
return LegacyAccessControlRole.ADMIN
|
|
|
|
|
if "dashboards:write" in permissions:
|
|
|
|
|
return LegacyAccessControlRole.EDITOR
|
|
|
|
|
if "dashboards:read" in permissions:
|
|
|
|
|
return LegacyAccessControlRole.VIEWER
|
|
|
|
|
return LegacyAccessControlRole.NONE
|
2024-03-18 10:16:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class IntegrationBacksyncAuthentication(BaseAuthentication):
|
|
|
|
|
model = IntegrationBacksyncAuthToken
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
def authenticate(self, request) -> typing.Tuple[ServerUser, IntegrationBacksyncAuthToken]:
|
2024-03-18 10:16:54 +01:00
|
|
|
token = get_authorization_header(request).decode("utf-8")
|
|
|
|
|
|
|
|
|
|
if not token:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token.")
|
|
|
|
|
|
|
|
|
|
return self.authenticate_credentials(token)
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
def authenticate_credentials(self, token_string: str) -> typing.Tuple[ServerUser, IntegrationBacksyncAuthToken]:
|
2024-03-18 10:16:54 +01:00
|
|
|
try:
|
|
|
|
|
auth_token = self.model.validate_token_string(token_string)
|
|
|
|
|
except InvalidToken:
|
|
|
|
|
raise exceptions.AuthenticationFailed("Invalid token")
|
|
|
|
|
|
|
|
|
|
if auth_token.organization.is_moved:
|
|
|
|
|
raise OrganizationMovedException(auth_token.organization)
|
|
|
|
|
if auth_token.organization.deleted_at:
|
|
|
|
|
raise OrganizationDeletedException(auth_token.organization)
|
|
|
|
|
|
|
|
|
|
user = ServerUser()
|
|
|
|
|
|
|
|
|
|
return user, auth_token
|