From cf7e53b006db2c269dae40fff5f499ed5700267e Mon Sep 17 00:00:00 2001 From: narasux Date: Thu, 11 Jul 2024 20:46:24 +0800 Subject: [PATCH 1/2] feat: add JustLeaveAppManager for better experience --- .../paasng/platform/applications/models.py | 44 ++++++++++++++++++- .../paasng/platform/applications/views.py | 7 +++ .../paasng/platform/evaluation/tasks.py | 7 ++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/apiserver/paasng/paasng/platform/applications/models.py b/apiserver/paasng/paasng/platform/applications/models.py index 2c05b6af23..86d2b67c39 100644 --- a/apiserver/paasng/paasng/platform/applications/models.py +++ b/apiserver/paasng/paasng/platform/applications/models.py @@ -19,7 +19,7 @@ import os import time import uuid -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union from bkstorages.backends.bkrepo import RequestError from django.conf import settings @@ -29,6 +29,7 @@ from pilkit.processors import ResizeToFill from paasng.core.core.storages.object_storage import app_logo_storage +from paasng.core.core.storages.redisdb import get_default_redis from paasng.core.region.models import get_region from paasng.infras.iam.helpers import fetch_role_members from paasng.infras.iam.permissions.resources.application import ApplicationPermission @@ -195,6 +196,41 @@ def process_order_by(order_by: List[str], queryset: QuerySet) -> QuerySet: return queryset.order_by(*fields) +class JustLeaveAppManager: + """ + 刚退出的应用管理器 + + Q:为什么需要有这个管理器 + A:开发者中心接入权限中心后,由于接入的是 RBAC 模型,导致有这么一个链路 + 用户退出权限中心用户组 ----异步任务----> 回收用户权限 + 这样会出现一个问题:用户退出应用后,短时间(30s)内还有这个应用的权限,体验不佳 + 这里的思路是:利用 Redis,缓存用户退出的应用 Code(5min),这段时间内,把这个应用 exclude 掉 + + Q:为什么所有方法都加 try-except + A:这个 manager 只是优化体验,如果 redis 挂了(虽然概率不大),也不该阻塞主流程 + """ + + EXPIRE_TIME = 300 + + def __init__(self, username: str): + self.redis_db = get_default_redis() + self.username = username + self.cache_key = f"bkpaas_just_leave_app_codes:{username}" + + def add(self, app_code: str) -> None: + try: + self.redis_db.rpush(self.cache_key, app_code) + self.redis_db.expire(self.cache_key, self.EXPIRE_TIME) + except Exception: + pass + + def list(self) -> Set[str]: + try: + return {x.decode() for x in self.redis_db.lrange(self.cache_key, 0, -1)} + except Exception: + return set() + + class UserApplicationFilter: """List user applications""" @@ -216,6 +252,12 @@ def filter( if order_by is None: order_by = [] applications = Application.objects.filter_by_user(self.user.pk, exclude_collaborated=exclude_collaborated) + + # 从缓存拿刚刚退出的应用 code exclude 掉,避免出现退出用户组,权限中心权限未同步的情况 + mgr = JustLeaveAppManager(get_username_by_bkpaas_user_id(self.user.pk)) + if just_leave_app_codes := mgr.list(): + applications = applications.exclude(code__in=just_leave_app_codes) + return BaseApplicationFilter.filter_queryset( applications, include_inactive=include_inactive, diff --git a/apiserver/paasng/paasng/platform/applications/views.py b/apiserver/paasng/paasng/platform/applications/views.py index 20794f3fbf..79ff9a4922 100644 --- a/apiserver/paasng/paasng/platform/applications/views.py +++ b/apiserver/paasng/paasng/platform/applications/views.py @@ -82,6 +82,7 @@ from paasng.platform.applications.models import ( Application, ApplicationEnvironment, + JustLeaveAppManager, UserApplicationFilter, UserMarkedApplication, ) @@ -822,6 +823,9 @@ def leave(self, request, *args, **kwargs): except BKIAMGatewayServiceError as e: raise error_codes.DELETE_APP_MEMBERS_ERROR.f(e.message) + # 将该应用 Code 标记为刚退出,避免出现退出用户组,权限中心权限未同步的情况 + JustLeaveAppManager(request.user.username).add(application.code) + sync_developers_to_sentry.delay(application.id) application_member_updated.send(sender=application, application=application) return Response(status=status.HTTP_204_NO_CONTENT) @@ -837,6 +841,9 @@ def destroy(self, request, *args, **kwargs): except BKIAMGatewayServiceError as e: raise error_codes.DELETE_APP_MEMBERS_ERROR.f(e.message) + # 将该应用 Code 标记为刚退出,避免出现退出用户组,权限中心权限未同步的情况 + JustLeaveAppManager(username).add(application.code) + sync_developers_to_sentry.delay(application.id) application_member_updated.send(sender=application, application=application) return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/paasng/paasng/platform/evaluation/tasks.py b/apiserver/paasng/paasng/platform/evaluation/tasks.py index 9e6155b5ea..3c6ffa0b00 100644 --- a/apiserver/paasng/paasng/platform/evaluation/tasks.py +++ b/apiserver/paasng/paasng/platform/evaluation/tasks.py @@ -27,7 +27,7 @@ from paasng.infras.iam.permissions.resources.application import ApplicationPermission from paasng.misc.operations.models import Operation from paasng.platform.applications.constants import ApplicationRole, ApplicationType -from paasng.platform.applications.models import Application +from paasng.platform.applications.models import Application, JustLeaveAppManager from paasng.platform.engine.constants import AppEnvName from paasng.platform.engine.models import Deployment from paasng.platform.evaluation.collectors import AppDeploymentCollector, AppResQuotaCollector, AppUserVisitCollector @@ -194,6 +194,11 @@ def send_idle_email_to_app_developers(app_codes: List[str], only_specified_users for idx, username in enumerate(waiting_notify_usernames): filters = ApplicationPermission().gen_develop_app_filters(username) app_codes = Application.objects.filter(filters).values_list("code", flat=True) + + # 从缓存拿刚刚退出的应用 code exclude 掉,避免出现退出用户组,权限中心权限未同步的情况 + if just_leave_app_codes := JustLeaveAppManager(username).list(): + app_codes = [c for c in app_codes if c not in just_leave_app_codes] + user_idle_app_reports = reports.filter(app__code__in=app_codes) if not user_idle_app_reports.exists(): From 130451ccbf2a23963c8eb96ecc72dca223766777 Mon Sep 17 00:00:00 2001 From: narasux Date: Fri, 12 Jul 2024 11:02:44 +0800 Subject: [PATCH 2/2] fix: resolve #1472 conversations --- .../paasng/infras/accounts/permissions/application.py | 4 ++-- apiserver/paasng/paasng/infras/iam/constants.py | 5 ----- .../paasng/infras/iam/permissions/resources/application.py | 4 ++-- apiserver/paasng/paasng/platform/applications/models.py | 4 +--- apiserver/paasng/paasng/settings/__init__.py | 6 ++++++ 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apiserver/paasng/paasng/infras/accounts/permissions/application.py b/apiserver/paasng/paasng/infras/accounts/permissions/application.py index 1311812be9..f090a978d5 100644 --- a/apiserver/paasng/paasng/infras/accounts/permissions/application.py +++ b/apiserver/paasng/paasng/infras/accounts/permissions/application.py @@ -19,11 +19,11 @@ import time from typing import Union +from django.conf import settings from iam.exceptions import AuthAPIError from rest_framework.exceptions import PermissionDenied from rest_framework.permissions import BasePermission -from paasng.infras.iam.constants import PERM_EXEMPT_TIME_FOR_OWNER_AFTER_CREATE_APP from paasng.infras.iam.helpers import user_group_apply_url from paasng.infras.iam.permissions.resources.application import AppAction, ApplicationPermission, AppPermCtx from paasng.platform.applications.models import Application @@ -71,7 +71,7 @@ def can_exempt_application_perm(user, application: Application) -> bool: # 因此需要在应用创建后的一定的时间内,对创建者(拥有应用最高权限)的操作进行权限豁免以保证功能可正常使用 return ( user.pk == application.owner - and time.time() - application.created.timestamp() < PERM_EXEMPT_TIME_FOR_OWNER_AFTER_CREATE_APP + and time.time() - application.created.timestamp() < settings.IAM_PERM_EFFECTIVE_TIMEDELTA ) diff --git a/apiserver/paasng/paasng/infras/iam/constants.py b/apiserver/paasng/paasng/infras/iam/constants.py index 148849686d..655442a6b6 100644 --- a/apiserver/paasng/paasng/infras/iam/constants.py +++ b/apiserver/paasng/paasng/infras/iam/constants.py @@ -136,8 +136,3 @@ class IAMErrorCodes(int, StructuredEnum): ], }, } - - -# 由于权限中心的用户组授权为异步行为,即创建用户组,添加用户,对组授权后需要等待一段时间(10-20秒左右)才能鉴权 -# 因此需要在应用创建后的一定的时间内,对创建者(拥有应用最高权限)的操作进行权限豁免以保证功能可正常使用 -PERM_EXEMPT_TIME_FOR_OWNER_AFTER_CREATE_APP = 5 * 60 diff --git a/apiserver/paasng/paasng/infras/iam/permissions/resources/application.py b/apiserver/paasng/paasng/infras/iam/permissions/resources/application.py index 55f296301d..4a8fa71224 100644 --- a/apiserver/paasng/paasng/infras/iam/permissions/resources/application.py +++ b/apiserver/paasng/paasng/infras/iam/permissions/resources/application.py @@ -27,7 +27,7 @@ from django.utils.translation import gettext_lazy as _ from iam.exceptions import AuthAPIError -from paasng.infras.iam.constants import PERM_EXEMPT_TIME_FOR_OWNER_AFTER_CREATE_APP, ResourceType +from paasng.infras.iam.constants import ResourceType from paasng.infras.iam.permissions.perm import PermCtx, Permission, ResCreatorAction, validate_empty from paasng.infras.iam.permissions.request import ResourceRequest @@ -224,7 +224,7 @@ def _gen_app_filters_by_request(self, request): # 因此在应用创建后的短时间内,需特殊豁免以免在列表页无法查询到最新的应用 perm_exempt_filter = Q( owner=user_id_encoder.encode(settings.USER_TYPE, request.subject.id), - created__gt=datetime.now() - timedelta(seconds=PERM_EXEMPT_TIME_FOR_OWNER_AFTER_CREATE_APP), + created__gt=datetime.now() - timedelta(seconds=settings.IAM_PERM_EFFECTIVE_TIMEDELTA), ) if not filters: return perm_exempt_filter diff --git a/apiserver/paasng/paasng/platform/applications/models.py b/apiserver/paasng/paasng/platform/applications/models.py index 86d2b67c39..439e00e0a4 100644 --- a/apiserver/paasng/paasng/platform/applications/models.py +++ b/apiserver/paasng/paasng/platform/applications/models.py @@ -210,8 +210,6 @@ class JustLeaveAppManager: A:这个 manager 只是优化体验,如果 redis 挂了(虽然概率不大),也不该阻塞主流程 """ - EXPIRE_TIME = 300 - def __init__(self, username: str): self.redis_db = get_default_redis() self.username = username @@ -220,7 +218,7 @@ def __init__(self, username: str): def add(self, app_code: str) -> None: try: self.redis_db.rpush(self.cache_key, app_code) - self.redis_db.expire(self.cache_key, self.EXPIRE_TIME) + self.redis_db.expire(self.cache_key, settings.IAM_PERM_EFFECTIVE_TIMEDELTA) except Exception: pass diff --git a/apiserver/paasng/paasng/settings/__init__.py b/apiserver/paasng/paasng/settings/__init__.py index cf56430364..afe2bb3310 100644 --- a/apiserver/paasng/paasng/settings/__init__.py +++ b/apiserver/paasng/paasng/settings/__init__.py @@ -616,6 +616,12 @@ def _build_file_handler(log_path: Path, filename: str, format: str) -> Dict: # 跳过初始化已有应用数据到权限中心(注意:仅跳过初始化数据,所有权限相关的操作还是依赖权限中心) BK_IAM_SKIP = settings.get("BK_IAM_SKIP", False) +# IAM 权限生效时间(单位:秒) +# 权限中心的用户组授权是异步行为,即创建用户组,添加用户,对组授权后需要等待一段时间(10-20秒左右)才能鉴权 +# 因此需要在应用创建后的一定的时间内,对创建者(拥有应用最高权限)的操作进行权限豁免以保证功能可正常使用 +# 退出用户组同理,因此在退出的一定时间内,需要先 exclude 掉避免退出后还可以看到应用的问题 +IAM_PERM_EFFECTIVE_TIMEDELTA = 5 * 60 + BKAUTH_DEFAULT_PROVIDER_TYPE = settings.get("BKAUTH_DEFAULT_PROVIDER_TYPE", "BK") # 蓝鲸的云 API 地址,用于内置环境变量的配置项