From febe1b2185eb89f41ec77449f8f153bb45d443d4 Mon Sep 17 00:00:00 2001 From: Michael Derynck Date: Thu, 20 Oct 2022 15:04:58 -0600 Subject: [PATCH] Add basic organization moved exception handling and middleware --- engine/apps/auth_token/auth.py | 11 +++++ .../mixins/alert_channel_defining_mixin.py | 5 +++ engine/apps/user_management/middlewares.py | 41 +++++++++++++++++++ .../migrations/0005_auto_20221020_1845.py | 32 +++++++++++++++ .../migrations/0006_alter_region_slug.py | 18 ++++++++ .../apps/user_management/models/__init__.py | 1 + .../user_management/models/organization.py | 1 + engine/apps/user_management/models/region.py | 20 ++++++++- engine/settings/base.py | 1 + 9 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 engine/apps/user_management/middlewares.py create mode 100644 engine/apps/user_management/migrations/0005_auto_20221020_1845.py create mode 100644 engine/apps/user_management/migrations/0006_alter_region_slug.py diff --git a/engine/apps/auth_token/auth.py b/engine/apps/auth_token/auth.py index 551116c6..1c92cebb 100644 --- a/engine/apps/auth_token/auth.py +++ b/engine/apps/auth_token/auth.py @@ -18,6 +18,7 @@ from .exceptions import InvalidToken from .models import ApiAuthToken, PluginAuthToken, ScheduleExportAuthToken, SlackAuthToken, UserScheduleExportAuthToken from .models.mobile_app_auth_token import MobileAppAuthToken from .models.mobile_app_verification_token import MobileAppVerificationToken +from ..user_management.models.region import OrganizationMovedException logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -46,6 +47,10 @@ class ApiTokenAuthentication(BaseAuthentication): auth_token = self.model.validate_token_string(token) except InvalidToken: raise exceptions.AuthenticationFailed("Invalid token.") + + if auth_token.organization.migration_destination is not None: + raise OrganizationMovedException(auth_token.organization) + return auth_token.user, auth_token @@ -167,6 +172,9 @@ class ScheduleExportAuthentication(BaseAuthentication): except InvalidToken: raise exceptions.AuthenticationFailed("Invalid token.") + if auth_token.organization.migration_destination is not None: + raise OrganizationMovedException(auth_token.organization) + if auth_token.schedule.public_primary_key != public_primary_key: raise exceptions.AuthenticationFailed("Invalid schedule export token for schedule") @@ -197,6 +205,9 @@ class UserScheduleExportAuthentication(BaseAuthentication): except InvalidToken: raise exceptions.AuthenticationFailed("Invalid token") + if auth_token.organization.migration_destination is not None: + raise OrganizationMovedException(auth_token.organization) + if auth_token.user.public_primary_key != public_primary_key: raise exceptions.AuthenticationFailed("Invalid schedule export token for user") diff --git a/engine/apps/integrations/mixins/alert_channel_defining_mixin.py b/engine/apps/integrations/mixins/alert_channel_defining_mixin.py index 3e1cc257..8bd79f51 100644 --- a/engine/apps/integrations/mixins/alert_channel_defining_mixin.py +++ b/engine/apps/integrations/mixins/alert_channel_defining_mixin.py @@ -7,6 +7,8 @@ from django.core.cache import cache from django.core.exceptions import PermissionDenied from django.db import OperationalError +from apps.user_management.models.region import OrganizationMovedException + logger = logging.getLogger(__name__) @@ -64,6 +66,9 @@ class AlertChannelDefiningMixin(object): logger.info("Cache is empty!") raise + if alert_receive_channel.organization.migration_destination is not None: + raise OrganizationMovedException(alert_receive_channel.organization) + del kwargs["alert_channel_key"] kwargs["alert_receive_channel"] = alert_receive_channel diff --git a/engine/apps/user_management/middlewares.py b/engine/apps/user_management/middlewares.py new file mode 100644 index 00000000..0fd6d4f3 --- /dev/null +++ b/engine/apps/user_management/middlewares.py @@ -0,0 +1,41 @@ +import logging +import re + +import requests +from django.http import HttpResponse +from django.utils.deprecation import MiddlewareMixin + +from apps.user_management.models.region import OrganizationMovedException +from common.api_helpers.utils import create_engine_url + +logger = logging.getLogger(__name__) + + +class OrganizationMovedMiddleware(MiddlewareMixin): + def process_exception(self, request, exception): + if isinstance(exception, OrganizationMovedException): + region = exception.organization.migration_destination + url = create_engine_url(request.path, override_base=region.oncall_backend_url) + if request.META['QUERY_STRING']: + url = f"{url}?{request.META['QUERY_STRING']}" + + regex = re.compile('^HTTP_') + headers = dict( + (regex.sub('', header), value) for (header, value) in request.META.items() if header.startswith('HTTP_') + ) + + if request.method == "GET": + response = requests.get(url, headers=headers) + elif request.method == "POST": + response = requests.post(url, data=request.body, headers=headers) + elif request.method == "PUT": + response = requests.put(url, data=request.body, headers=headers) + elif request.method == "DELETE": + response = requests.delete(url, headers=headers) + elif request.method == "OPTIONS": + response = requests.options(url, headers=headers) + + response.raise_for_status() + + return HttpResponse(response.content, status=response.status_code) + diff --git a/engine/apps/user_management/migrations/0005_auto_20221020_1845.py b/engine/apps/user_management/migrations/0005_auto_20221020_1845.py new file mode 100644 index 00000000..7cfe249b --- /dev/null +++ b/engine/apps/user_management/migrations/0005_auto_20221020_1845.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.15 on 2022-10-20 18:45 + +import apps.user_management.models.region +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_management', '0004_organization_region_slug'), + ] + + operations = [ + migrations.CreateModel( + name='Region', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('public_primary_key', models.CharField(default=apps.user_management.models.region.generate_public_primary_key_for_region, max_length=20, unique=True, validators=[django.core.validators.MinLengthValidator(13)])), + ('name', models.CharField(max_length=300)), + ('slug', models.CharField(max_length=300, unique=True)), + ('oncall_backend_url', models.URLField()), + ('is_default', models.BooleanField(default=False)), + ], + ), + migrations.AddField( + model_name='organization', + name='migration_destination', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='regions', to='user_management.region'), + ), + ] diff --git a/engine/apps/user_management/migrations/0006_alter_region_slug.py b/engine/apps/user_management/migrations/0006_alter_region_slug.py new file mode 100644 index 00000000..780cc9b7 --- /dev/null +++ b/engine/apps/user_management/migrations/0006_alter_region_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.15 on 2022-10-20 18:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_management', '0005_auto_20221020_1845'), + ] + + operations = [ + migrations.AlterField( + model_name='region', + name='slug', + field=models.CharField(max_length=50, unique=True), + ), + ] diff --git a/engine/apps/user_management/models/__init__.py b/engine/apps/user_management/models/__init__.py index 95ed32ab..e2bcd4c7 100644 --- a/engine/apps/user_management/models/__init__.py +++ b/engine/apps/user_management/models/__init__.py @@ -1,3 +1,4 @@ from .user import User # noqa: F401, isort: skip from .organization import Organization # noqa: F401 +from .region import Region # noqa: F401 from .team import Team # noqa: F401 diff --git a/engine/apps/user_management/models/organization.py b/engine/apps/user_management/models/organization.py index 0b0f0662..c1ba8316 100644 --- a/engine/apps/user_management/models/organization.py +++ b/engine/apps/user_management/models/organization.py @@ -59,6 +59,7 @@ class Organization(MaintainableObject): on_delete=models.SET_NULL, related_name="regions", default=None, + null=True, ) grafana_url = models.URLField() diff --git a/engine/apps/user_management/models/region.py b/engine/apps/user_management/models/region.py index 1b9b2cca..b41f4220 100644 --- a/engine/apps/user_management/models/region.py +++ b/engine/apps/user_management/models/region.py @@ -1,10 +1,24 @@ +import logging + from django.conf import settings from django.core.validators import MinLengthValidator from django.db import models +from rest_framework.request import Request +from rest_framework.response import Response +from apps.user_management.models import Organization from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +logger = logging.getLogger(__name__) + + +class OrganizationMovedException(Exception): + + def __init__(self, organization: Organization): + self.organization = organization + + def generate_public_primary_key_for_region(): prefix = "R" new_public_primary_key = generate_public_primary_key(prefix) @@ -19,6 +33,10 @@ def generate_public_primary_key_for_region(): return new_public_primary_key +def redirect_organization_request(organization: Organization, request: Request): + logger.info("**** Redirect! ****") + + class Region(models.Model): public_primary_key = models.CharField( max_length=20, @@ -28,6 +46,6 @@ class Region(models.Model): ) name = models.CharField(max_length=300) - slug = models.CharField(max_length=300, unique=True) + slug = models.CharField(max_length=50, unique=True) oncall_backend_url = models.URLField() is_default = models.BooleanField(default=False) diff --git a/engine/settings/base.py b/engine/settings/base.py index b8d4e9cb..95c090de 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -237,6 +237,7 @@ MIDDLEWARE = [ "social_django.middleware.SocialAuthExceptionMiddleware", "apps.social_auth.middlewares.SocialAuthAuthCanceledExceptionMiddleware", "apps.integrations.middlewares.IntegrationExceptionMiddleware", + "apps.user_management.middlewares.OrganizationMovedMiddleware", ] LOG_REQUEST_ID_HEADER = "HTTP_X_CLOUD_TRACE_CONTEXT"