From 1062f7d6b203b602edbe9b88ad34c7806bcaf5a9 Mon Sep 17 00:00:00 2001 From: jamesge Date: Tue, 12 Oct 2021 10:22:39 +0800 Subject: [PATCH] Iamv3 framework (#1580) * feat: project, cluster, namespace based and templateset on iam v3 * feat: add project & namespace iam api * minor: update license * chore: add requests_mock * minor: fix review --- bcs-app/backend/api_urls.py | 2 +- .../bcs_web/iam/bcs_iam_migration/__init__.py | 1 - .../bcs_iam_migration/migrations/__init__.py | 1 - .../backend/bcs_web/iam/open_apis/__init__.py | 1 - .../iam/open_apis/resources/__init__.py | 1 - .../iam/open_apis/resources/project.py | 49 ---- .../iam/open_apis/resources/provider.py | 64 ----- .../bcs_web/iam/open_apis/v1/__init__.py | 1 - bcs-app/backend/bcs_web/permissions.py | 2 +- bcs-app/backend/components/paas_auth.py | 2 +- bcs-app/backend/components/paas_cc.py | 9 +- .../container_service/projects/base/utils.py | 2 +- .../projects/open_apis/views.py | 4 +- .../container_service/projects/serializers.py | 2 +- .../container_service/projects/views.py | 8 +- .../open_apis/v1/views.py => iam/__init__.py} | 5 - .../bcs_iam_migration/__init__.py} | 5 - .../iam/bcs_iam_migration/apps.py | 3 +- .../migrations/0001_initial.py | 4 +- .../0002_bk_bcs_app_202108181450.py | 35 +++ .../0003_bk_bcs_app_202108181523.py | 35 +++ .../0004_bk_bcs_app_202108181524.py | 35 +++ .../0005_bk_bcs_app_202108181525.py | 35 +++ .../0006_bk_bcs_app_202108181525.py | 35 +++ .../0007_bk_bcs_app_202108262047.py | 35 +++ .../bcs_iam_migration/migrations/__init__.py | 14 ++ .../permissions.py => iam/legacy_perms.py} | 13 +- bcs-app/backend/iam/open_apis/__init__.py | 14 ++ .../iam/open_apis/authentication.py | 13 +- .../{bcs_web => }/iam/open_apis/constants.py | 28 +-- .../{bcs_web => }/iam/open_apis/exceptions.py | 3 - .../iam/open_apis/provider/__init__.py | 14 ++ .../iam/open_apis/provider/namespace.py | 70 ++++++ .../backend/iam/open_apis/provider/project.py | 47 ++++ .../iam/open_apis/provider/resource.py | 76 ++++++ .../backend/iam/open_apis/provider/utils.py | 20 ++ .../iam/open_apis/serializers.py | 10 +- .../{bcs_web => }/iam/open_apis/urls.py | 2 +- bcs-app/backend/iam/open_apis/v1/__init__.py | 14 ++ bcs-app/backend/iam/open_apis/v1/urls.py | 23 ++ .../{bcs_web => }/iam/open_apis/views.py | 10 +- bcs-app/backend/iam/permissions/__init__.py | 14 ++ bcs-app/backend/iam/permissions/apply_url.py | 45 ++++ bcs-app/backend/iam/permissions/client.py | 102 ++++++++ bcs-app/backend/iam/permissions/decorators.py | 141 +++++++++++ bcs-app/backend/iam/permissions/exceptions.py | 58 +++++ bcs-app/backend/iam/permissions/perm.py | 180 +++++++++++++++ bcs-app/backend/iam/permissions/request.py | 98 ++++++++ .../iam/permissions/resources/__init__.py | 14 ++ .../iam/permissions/resources/cluster.py | 117 ++++++++++ .../iam/permissions/resources/constants.py | 22 ++ .../iam/permissions/resources/namespace.py | 144 ++++++++++++ .../iam/permissions/resources/project.py | 82 +++++++ .../iam/permissions/resources/templateset.py | 126 ++++++++++ bcs-app/backend/settings/ce/base.py | 2 +- bcs-app/backend/settings/ce/dev.py | 2 +- .../tests/bcs_mocks/data/paas_cc_json.py | 72 ++++++ bcs-app/backend/tests/bcs_mocks/misc.py | 13 +- .../backend/tests/components/test_paas_cc.py | 8 + .../tests/components/test_permissions.py | 2 +- .../{bcs_web => tests}/iam/__init__.py | 0 bcs-app/backend/tests/iam/conftest.py | 65 ++++++ bcs-app/backend/tests/iam/fake_iam.py | 95 ++++++++ .../backend/tests/iam/open_apis/__init__.py | 14 ++ .../backend/tests/iam/open_apis/conftest.py | 26 +++ .../tests/iam/open_apis/test_namespace.py | 63 +++++ .../tests/iam/open_apis/test_project.py | 55 +++++ .../backend/tests/iam/permissions/__init__.py | 14 ++ .../backend/tests/iam/permissions/conftest.py | 34 +++ .../backend/tests/iam/permissions/roles.py | 40 ++++ .../tests/iam/permissions/test_cluster.py | 218 ++++++++++++++++++ .../tests/iam/permissions/test_namespace.py | 206 +++++++++++++++++ .../tests/iam/permissions/test_project.py | 98 ++++++++ .../tests/iam/permissions/test_templateset.py | 136 +++++++++++ .../tests/testing_utils/mocks/paas_cc.py | 38 +++ bcs-app/backend/utils/error_codes.py | 4 - bcs-app/backend/utils/permissions.py | 2 +- bcs-app/poetry.lock | 56 ++++- bcs-app/pyproject.toml | 1 + .../support-files/iam/0002_project_extra.json | 44 ++++ bcs-app/support-files/iam/0003_cluster.json | 156 +++++++++++++ bcs-app/support-files/iam/0004_namespace.json | 157 +++++++++++++ .../support-files/iam/0005_templateset.json | 182 +++++++++++++++ .../support-files/iam/0006_action_groups.json | 96 ++++++++ .../iam/0007_resource_creator_actions.json | 80 +++++++ 85 files changed, 3673 insertions(+), 196 deletions(-) delete mode 100644 bcs-app/backend/bcs_web/iam/bcs_iam_migration/__init__.py delete mode 100644 bcs-app/backend/bcs_web/iam/bcs_iam_migration/migrations/__init__.py delete mode 100644 bcs-app/backend/bcs_web/iam/open_apis/__init__.py delete mode 100644 bcs-app/backend/bcs_web/iam/open_apis/resources/__init__.py delete mode 100644 bcs-app/backend/bcs_web/iam/open_apis/resources/project.py delete mode 100644 bcs-app/backend/bcs_web/iam/open_apis/resources/provider.py delete mode 100644 bcs-app/backend/bcs_web/iam/open_apis/v1/__init__.py rename bcs-app/backend/{bcs_web/iam/open_apis/v1/views.py => iam/__init__.py} (89%) rename bcs-app/backend/{bcs_web/iam/open_apis/v1/urls.py => iam/bcs_iam_migration/__init__.py} (86%) rename bcs-app/backend/{bcs_web => }/iam/bcs_iam_migration/apps.py (92%) rename bcs-app/backend/{bcs_web => }/iam/bcs_iam_migration/migrations/0001_initial.py (94%) create mode 100644 bcs-app/backend/iam/bcs_iam_migration/migrations/0002_bk_bcs_app_202108181450.py create mode 100644 bcs-app/backend/iam/bcs_iam_migration/migrations/0003_bk_bcs_app_202108181523.py create mode 100644 bcs-app/backend/iam/bcs_iam_migration/migrations/0004_bk_bcs_app_202108181524.py create mode 100644 bcs-app/backend/iam/bcs_iam_migration/migrations/0005_bk_bcs_app_202108181525.py create mode 100644 bcs-app/backend/iam/bcs_iam_migration/migrations/0006_bk_bcs_app_202108181525.py create mode 100644 bcs-app/backend/iam/bcs_iam_migration/migrations/0007_bk_bcs_app_202108262047.py create mode 100644 bcs-app/backend/iam/bcs_iam_migration/migrations/__init__.py rename bcs-app/backend/{bcs_web/iam/permissions.py => iam/legacy_perms.py} (97%) create mode 100644 bcs-app/backend/iam/open_apis/__init__.py rename bcs-app/backend/{bcs_web => }/iam/open_apis/authentication.py (85%) rename bcs-app/backend/{bcs_web => }/iam/open_apis/constants.py (57%) rename bcs-app/backend/{bcs_web => }/iam/open_apis/exceptions.py (94%) create mode 100644 bcs-app/backend/iam/open_apis/provider/__init__.py create mode 100644 bcs-app/backend/iam/open_apis/provider/namespace.py create mode 100644 bcs-app/backend/iam/open_apis/provider/project.py create mode 100644 bcs-app/backend/iam/open_apis/provider/resource.py create mode 100644 bcs-app/backend/iam/open_apis/provider/utils.py rename bcs-app/backend/{bcs_web => }/iam/open_apis/serializers.py (74%) rename bcs-app/backend/{bcs_web => }/iam/open_apis/urls.py (91%) create mode 100644 bcs-app/backend/iam/open_apis/v1/__init__.py create mode 100644 bcs-app/backend/iam/open_apis/v1/urls.py rename bcs-app/backend/{bcs_web => }/iam/open_apis/views.py (82%) create mode 100644 bcs-app/backend/iam/permissions/__init__.py create mode 100644 bcs-app/backend/iam/permissions/apply_url.py create mode 100644 bcs-app/backend/iam/permissions/client.py create mode 100644 bcs-app/backend/iam/permissions/decorators.py create mode 100644 bcs-app/backend/iam/permissions/exceptions.py create mode 100644 bcs-app/backend/iam/permissions/perm.py create mode 100644 bcs-app/backend/iam/permissions/request.py create mode 100644 bcs-app/backend/iam/permissions/resources/__init__.py create mode 100644 bcs-app/backend/iam/permissions/resources/cluster.py create mode 100644 bcs-app/backend/iam/permissions/resources/constants.py create mode 100644 bcs-app/backend/iam/permissions/resources/namespace.py create mode 100644 bcs-app/backend/iam/permissions/resources/project.py create mode 100644 bcs-app/backend/iam/permissions/resources/templateset.py rename bcs-app/backend/{bcs_web => tests}/iam/__init__.py (100%) create mode 100644 bcs-app/backend/tests/iam/conftest.py create mode 100644 bcs-app/backend/tests/iam/fake_iam.py create mode 100644 bcs-app/backend/tests/iam/open_apis/__init__.py create mode 100644 bcs-app/backend/tests/iam/open_apis/conftest.py create mode 100644 bcs-app/backend/tests/iam/open_apis/test_namespace.py create mode 100644 bcs-app/backend/tests/iam/open_apis/test_project.py create mode 100644 bcs-app/backend/tests/iam/permissions/__init__.py create mode 100644 bcs-app/backend/tests/iam/permissions/conftest.py create mode 100644 bcs-app/backend/tests/iam/permissions/roles.py create mode 100644 bcs-app/backend/tests/iam/permissions/test_cluster.py create mode 100644 bcs-app/backend/tests/iam/permissions/test_namespace.py create mode 100644 bcs-app/backend/tests/iam/permissions/test_project.py create mode 100644 bcs-app/backend/tests/iam/permissions/test_templateset.py create mode 100644 bcs-app/support-files/iam/0002_project_extra.json create mode 100644 bcs-app/support-files/iam/0003_cluster.json create mode 100644 bcs-app/support-files/iam/0004_namespace.json create mode 100644 bcs-app/support-files/iam/0005_templateset.json create mode 100644 bcs-app/support-files/iam/0006_action_groups.json create mode 100644 bcs-app/support-files/iam/0007_resource_creator_actions.json diff --git a/bcs-app/backend/api_urls.py b/bcs-app/backend/api_urls.py index 1755451d5..40e4281c0 100644 --- a/bcs-app/backend/api_urls.py +++ b/bcs-app/backend/api_urls.py @@ -40,7 +40,7 @@ include("backend.templatesets.open_apis.template_urls"), ), # 提供给iam拉取资源实例的url(已注册到iam后台) - url(r"^iam/", include("backend.bcs_web.iam.open_apis.urls")), + url(r"^iam/", include("backend.iam.open_apis.urls")), # web_console API url( r"^projects/(?P[\w\-]+)/clusters/(?P[\w\-]+)/web_console/sessions/", diff --git a/bcs-app/backend/bcs_web/iam/bcs_iam_migration/__init__.py b/bcs-app/backend/bcs_web/iam/bcs_iam_migration/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/bcs-app/backend/bcs_web/iam/bcs_iam_migration/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/bcs-app/backend/bcs_web/iam/bcs_iam_migration/migrations/__init__.py b/bcs-app/backend/bcs_web/iam/bcs_iam_migration/migrations/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/bcs-app/backend/bcs_web/iam/bcs_iam_migration/migrations/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/bcs-app/backend/bcs_web/iam/open_apis/__init__.py b/bcs-app/backend/bcs_web/iam/open_apis/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/bcs-app/backend/bcs_web/iam/open_apis/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/bcs-app/backend/bcs_web/iam/open_apis/resources/__init__.py b/bcs-app/backend/bcs_web/iam/open_apis/resources/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/bcs-app/backend/bcs_web/iam/open_apis/resources/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/bcs-app/backend/bcs_web/iam/open_apis/resources/project.py b/bcs-app/backend/bcs_web/iam/open_apis/resources/project.py deleted file mode 100644 index b92b34cec..000000000 --- a/bcs-app/backend/bcs_web/iam/open_apis/resources/project.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community -Edition) available. -Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. -Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://opensource.org/licenses/MIT - -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on -an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. -""" -try: - from iam.resource.provider import ListResult, ResourceProvider -except Exception: - pass -from backend.components import ssm -from backend.container_service.projects.base import filter_projects - - -class ProjectProvider(ResourceProvider): - def list_attr(self, **options): - return ListResult(results=[], count=0) - - def list_attr_value(self, filter, page, **options): - return ListResult(results=[], count=0) - - def list_instance(self, filter, page, **options): - access_token = ssm.get_client_access_token()["access_token"] - projects = filter_projects(access_token) - count = len(projects) - projects = projects[page.slice_from : page.slice_to] # noqa - results = [{"id": p["project_id"], "display_name": p["project_name"]} for p in projects] - return ListResult(results=results, count=count) - - def fetch_instance_info(self, filter, **options): - access_token = ssm.get_client_access_token()["access_token"] - query_params = None - if filter.ids: - query_params = {"project_ids": ",".join(filter.ids)} - projects = filter_projects(access_token, query_params) - results = [{"id": p["project_id"], "display_name": p["project_name"]} for p in projects] - return ListResult(results=results, count=len(results)) - - def list_instance_by_policy(self, filter, page, **options): - # TODO 确认基于实例的查询是不是就是id的过滤查询 - return ListResult(results=[], count=0) diff --git a/bcs-app/backend/bcs_web/iam/open_apis/resources/provider.py b/bcs-app/backend/bcs_web/iam/open_apis/resources/provider.py deleted file mode 100644 index 7558e247f..000000000 --- a/bcs-app/backend/bcs_web/iam/open_apis/resources/provider.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community -Edition) available. -Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. -Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://opensource.org/licenses/MIT - -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on -an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. -""" -try: - from iam.resource.utils import get_filter_obj, get_page_obj -except Exception: - pass - -from ..exceptions import ResNotFoundError -from .project import ProjectProvider - -PROVIDER_CLS_MAP = {"project": ProjectProvider} - - -class BCSResourceProvider: - def __init__(self, resource_type): - try: - self.resource_provider = PROVIDER_CLS_MAP.get(resource_type)() - except Exception: - raise ResNotFoundError(f"unsupported resource type: {resource_type}") - - def _parse_filter_and_page(self, data): - filter_obj = get_filter_obj(data["filter"], ["ids", "parent", "search", "resource_type_chain"]) - page_obj = get_page_obj(data.get("page")) - return filter_obj, page_obj - - def provide(self, data, **options): - handler = getattr(self, data["method"]) - return handler(data, **options) - - def list_attr(self, data, **options): - result = self.resource_provider.list_attr(**options) - return result.to_list() - - def list_attr_value(self, data, **options): - filter, page = self._parse_filter_and_page(data) - result = self.resource_provider.list_attr_value(filter, page, **options) - return result.to_dict() - - def list_instance(self, data, **options): - filter, page = self._parse_filter_and_page(data) - result = self.resource_provider.list_instance(filter, page, **options) - return result.to_dict() - - def fetch_instance_info(self, data, **options): - filter, _ = self._parse_filter_and_page(data) - result = self.resource_provider.fetch_instance_info(filter, **options) - return result.to_list() - - def list_instance_by_policy(self, data, **options): - filter, page = self._parse_filter_and_page(data) - result = self.resource_provider.list_instance_by_policy(filter, page, **options) - return result.to_list() diff --git a/bcs-app/backend/bcs_web/iam/open_apis/v1/__init__.py b/bcs-app/backend/bcs_web/iam/open_apis/v1/__init__.py deleted file mode 100644 index 40a96afc6..000000000 --- a/bcs-app/backend/bcs_web/iam/open_apis/v1/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/bcs-app/backend/bcs_web/permissions.py b/bcs-app/backend/bcs_web/permissions.py index acb6a66f4..a9baeb6e1 100644 --- a/bcs-app/backend/bcs_web/permissions.py +++ b/bcs-app/backend/bcs_web/permissions.py @@ -19,11 +19,11 @@ from backend.accounts import bcs_perm from backend.bcs_web.audit_log.audit.context import AuditContext -from backend.bcs_web.iam import permissions from backend.components.base import ComponentAuth from backend.components.paas_cc import PaaSCCClient from backend.container_service.clusters.base.models import CtxCluster from backend.container_service.projects.base.models import CtxProject +from backend.iam import legacy_perms as permissions from backend.utils import FancyDict from backend.utils.cache import region diff --git a/bcs-app/backend/components/paas_auth.py b/bcs-app/backend/components/paas_auth.py index 29d7c3b16..7beeb0b64 100644 --- a/bcs-app/backend/components/paas_auth.py +++ b/bcs-app/backend/components/paas_auth.py @@ -14,7 +14,7 @@ """ import logging -from backend.bcs_web.iam import permissions +from backend.iam import legacy_perms as permissions from .ssm import get_client_access_token diff --git a/bcs-app/backend/components/paas_cc.py b/bcs-app/backend/components/paas_cc.py index 80997f625..2cd6ca5c2 100644 --- a/bcs-app/backend/components/paas_cc.py +++ b/bcs-app/backend/components/paas_cc.py @@ -20,10 +20,10 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ -from backend.bcs_web.iam import permissions from backend.components.base import BaseHttpClient, BkApiClient, ComponentAuth, response_handler from backend.components.utils import http_delete, http_get, http_patch, http_post, http_put from backend.container_service.clusters.models import CommonStatus +from backend.iam import legacy_perms as permissions from backend.utils.basic import getitems from backend.utils.decorators import parse_response_data from backend.utils.errcodes import ErrorCode @@ -523,6 +523,7 @@ def __init__(self, host: str): # PaaSCC 系统接口地址 self.get_cluster_url = f"{host}/projects/{{project_id}}/clusters/{{cluster_id}}" + self.get_cluster_by_id_url = f"{host}/clusters/{{cluster_id}}/" self.get_project_url = f"{host}/projects/{{project_id}}/" self.update_cluster_url = f"{host}/projects/{{project_id}}/clusters/{{cluster_id}}/" self.delete_cluster_url = f"{host}/projects/{{project_id}}/clusters/{{cluster_id}}/" @@ -552,6 +553,12 @@ def get_cluster(self, project_id: str, cluster_id: str) -> Dict: url = self._config.get_cluster_url.format(project_id=project_id, cluster_id=cluster_id) return self._client.request_json('GET', url) + @response_handler() + def get_cluster_by_id(self, cluster_id: str) -> Dict: + """根据集群ID获取集群信息""" + url = self._config.get_cluster_by_id_url.format(cluster_id=cluster_id) + return self._client.request_json('GET', url) + @parse_response_data() def get_project(self, project_id: str) -> Dict: """获取项目信息""" diff --git a/bcs-app/backend/container_service/projects/base/utils.py b/bcs-app/backend/container_service/projects/base/utils.py index 2dac8ca69..e97611052 100644 --- a/bcs-app/backend/container_service/projects/base/utils.py +++ b/bcs-app/backend/container_service/projects/base/utils.py @@ -26,7 +26,7 @@ def query_projects(access_token, query_params=None): return paas_cc.get_projects(access_token, query_params) -def filter_projects(access_token, query_params=None): +def list_projects(access_token, query_params=None): data = query_projects(access_token, query_params) projects = data.get("results") or [] # 为了兼容导航的参数要求, 增加了project_code字段 diff --git a/bcs-app/backend/container_service/projects/open_apis/views.py b/bcs-app/backend/container_service/projects/open_apis/views.py index 7a92543cc..3f784afac 100644 --- a/bcs-app/backend/container_service/projects/open_apis/views.py +++ b/bcs-app/backend/container_service/projects/open_apis/views.py @@ -16,7 +16,7 @@ from backend.bcs_web.apis.authentication import JWTAuthentication from backend.bcs_web.apis.permissions import AccessTokenPermission -from backend.container_service.projects.base import filter_projects +from backend.container_service.projects.base import list_projects from backend.container_service.projects.views import NavProjectPermissionViewSet from backend.utils.renderers import BKAPIRenderer @@ -27,5 +27,5 @@ class ProjectsViewSet(NavProjectPermissionViewSet): permission_classes = (AccessTokenPermission,) def list_projects(self, request): - projects = filter_projects(request.user.token.access_token) + projects = list_projects(request.user.token.access_token) return Response(projects) diff --git a/bcs-app/backend/container_service/projects/serializers.py b/bcs-app/backend/container_service/projects/serializers.py index 5cf032238..f33b55026 100644 --- a/bcs-app/backend/container_service/projects/serializers.py +++ b/bcs-app/backend/container_service/projects/serializers.py @@ -14,8 +14,8 @@ """ from rest_framework import serializers -from backend.bcs_web.iam.permissions import ProjectActions from backend.container_service.projects.base.constants import ProjectKindID +from backend.iam.legacy_perms import ProjectActions class UpdateProjectNewSLZ(serializers.Serializer): diff --git a/bcs-app/backend/container_service/projects/views.py b/bcs-app/backend/container_service/projects/views.py index 4b8fde312..9d00e848c 100644 --- a/bcs-app/backend/container_service/projects/views.py +++ b/bcs-app/backend/container_service/projects/views.py @@ -24,11 +24,11 @@ from backend.bcs_web.audit_log import client from backend.bcs_web.constants import bcs_project_cache_key -from backend.bcs_web.iam.permissions import ProjectPermission from backend.bcs_web.viewsets import SystemViewSet from backend.components import cc, paas_cc from backend.container_service.projects import base as Project from backend.container_service.projects.utils import fetch_has_maintain_perm_apps, update_bcs_service_for_project +from backend.iam.legacy_perms import ProjectPermission from backend.utils.basic import normalize_datetime from backend.utils.cache import region from backend.utils.errcodes import ErrorCode @@ -229,11 +229,11 @@ def filter_projects(self, request): access_token = request.user.token.access_token if project_code: - projects = Project.filter_projects(access_token, {"english_names": project_code}) + projects = Project.list_projects(access_token, {"english_names": project_code}) elif project_name: - projects = Project.filter_projects(access_token, {"project_names": project_name}) + projects = Project.list_projects(access_token, {"project_names": project_name}) else: - projects = Project.filter_projects(access_token) + projects = Project.list_projects(access_token) if not projects: return Response(projects) diff --git a/bcs-app/backend/bcs_web/iam/open_apis/v1/views.py b/bcs-app/backend/iam/__init__.py similarity index 89% rename from bcs-app/backend/bcs_web/iam/open_apis/v1/views.py rename to bcs-app/backend/iam/__init__.py index 59a1e9c91..08f8f6ab8 100644 --- a/bcs-app/backend/bcs_web/iam/open_apis/v1/views.py +++ b/bcs-app/backend/iam/__init__.py @@ -12,8 +12,3 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -from ..views import ResourceAPIView - - -class ProjectAPIView(ResourceAPIView): - pass diff --git a/bcs-app/backend/bcs_web/iam/open_apis/v1/urls.py b/bcs-app/backend/iam/bcs_iam_migration/__init__.py similarity index 86% rename from bcs-app/backend/bcs_web/iam/open_apis/v1/urls.py rename to bcs-app/backend/iam/bcs_iam_migration/__init__.py index f1bb3b727..08f8f6ab8 100644 --- a/bcs-app/backend/bcs_web/iam/open_apis/v1/urls.py +++ b/bcs-app/backend/iam/bcs_iam_migration/__init__.py @@ -12,8 +12,3 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -from django.conf.urls import url - -from . import views - -urlpatterns = [url(r"^projects/", views.ProjectAPIView.as_view())] diff --git a/bcs-app/backend/bcs_web/iam/bcs_iam_migration/apps.py b/bcs-app/backend/iam/bcs_iam_migration/apps.py similarity index 92% rename from bcs-app/backend/bcs_web/iam/bcs_iam_migration/apps.py rename to bcs-app/backend/iam/bcs_iam_migration/apps.py index 4618ee720..e745c8562 100644 --- a/bcs-app/backend/bcs_web/iam/bcs_iam_migration/apps.py +++ b/bcs-app/backend/iam/bcs_iam_migration/apps.py @@ -18,4 +18,5 @@ class BcsIamMigrationConfig(AppConfig): - name = "backend.bcs_web.iam.bcs_iam_migration" + name = "backend.iam.bcs_iam_migration" + label = "bcs_iam_migration" diff --git a/bcs-app/backend/bcs_web/iam/bcs_iam_migration/migrations/0001_initial.py b/bcs-app/backend/iam/bcs_iam_migration/migrations/0001_initial.py similarity index 94% rename from bcs-app/backend/bcs_web/iam/bcs_iam_migration/migrations/0001_initial.py rename to bcs-app/backend/iam/bcs_iam_migration/migrations/0001_initial.py index df90f86df..128bc875f 100644 --- a/bcs-app/backend/bcs_web/iam/bcs_iam_migration/migrations/0001_initial.py +++ b/bcs-app/backend/iam/bcs_iam_migration/migrations/0001_initial.py @@ -32,6 +32,4 @@ class Migration(migrations.Migration): dependencies = [] - operations = [ - migrations.RunPython(forward_func) - ] + operations = [migrations.RunPython(forward_func)] diff --git a/bcs-app/backend/iam/bcs_iam_migration/migrations/0002_bk_bcs_app_202108181450.py b/bcs-app/backend/iam/bcs_iam_migration/migrations/0002_bk_bcs_app_202108181450.py new file mode 100644 index 000000000..b869146cd --- /dev/null +++ b/bcs-app/backend/iam/bcs_iam_migration/migrations/0002_bk_bcs_app_202108181450.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import codecs +import json +import os + +from django.conf import settings +from django.db import migrations +from iam.contrib.iam_migration.migrator import IAMMigrator + + +def forward_func(apps, schema_editor): + + migrator = IAMMigrator(Migration.migration_json) + migrator.migrate() + + +class Migration(migrations.Migration): + migration_json = "0002_project_extra.json" + + dependencies = [('bcs_iam_migration', '0001_initial')] + + operations = [migrations.RunPython(forward_func)] diff --git a/bcs-app/backend/iam/bcs_iam_migration/migrations/0003_bk_bcs_app_202108181523.py b/bcs-app/backend/iam/bcs_iam_migration/migrations/0003_bk_bcs_app_202108181523.py new file mode 100644 index 000000000..e8cbe960a --- /dev/null +++ b/bcs-app/backend/iam/bcs_iam_migration/migrations/0003_bk_bcs_app_202108181523.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import codecs +import json +import os + +from django.conf import settings +from django.db import migrations +from iam.contrib.iam_migration.migrator import IAMMigrator + + +def forward_func(apps, schema_editor): + + migrator = IAMMigrator(Migration.migration_json) + migrator.migrate() + + +class Migration(migrations.Migration): + migration_json = "0003_cluster.json" + + dependencies = [('bcs_iam_migration', '0002_bk_bcs_app_202108181450')] + + operations = [migrations.RunPython(forward_func)] diff --git a/bcs-app/backend/iam/bcs_iam_migration/migrations/0004_bk_bcs_app_202108181524.py b/bcs-app/backend/iam/bcs_iam_migration/migrations/0004_bk_bcs_app_202108181524.py new file mode 100644 index 000000000..765aa7f2c --- /dev/null +++ b/bcs-app/backend/iam/bcs_iam_migration/migrations/0004_bk_bcs_app_202108181524.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import codecs +import json +import os + +from django.conf import settings +from django.db import migrations +from iam.contrib.iam_migration.migrator import IAMMigrator + + +def forward_func(apps, schema_editor): + + migrator = IAMMigrator(Migration.migration_json) + migrator.migrate() + + +class Migration(migrations.Migration): + migration_json = "0004_namespace.json" + + dependencies = [('bcs_iam_migration', '0003_bk_bcs_app_202108181523')] + + operations = [migrations.RunPython(forward_func)] diff --git a/bcs-app/backend/iam/bcs_iam_migration/migrations/0005_bk_bcs_app_202108181525.py b/bcs-app/backend/iam/bcs_iam_migration/migrations/0005_bk_bcs_app_202108181525.py new file mode 100644 index 000000000..6887db145 --- /dev/null +++ b/bcs-app/backend/iam/bcs_iam_migration/migrations/0005_bk_bcs_app_202108181525.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import codecs +import json +import os + +from django.conf import settings +from django.db import migrations +from iam.contrib.iam_migration.migrator import IAMMigrator + + +def forward_func(apps, schema_editor): + + migrator = IAMMigrator(Migration.migration_json) + migrator.migrate() + + +class Migration(migrations.Migration): + migration_json = "0005_templateset.json" + + dependencies = [('bcs_iam_migration', '0004_bk_bcs_app_202108181524')] + + operations = [migrations.RunPython(forward_func)] diff --git a/bcs-app/backend/iam/bcs_iam_migration/migrations/0006_bk_bcs_app_202108181525.py b/bcs-app/backend/iam/bcs_iam_migration/migrations/0006_bk_bcs_app_202108181525.py new file mode 100644 index 000000000..63b5d80b8 --- /dev/null +++ b/bcs-app/backend/iam/bcs_iam_migration/migrations/0006_bk_bcs_app_202108181525.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import codecs +import json +import os + +from django.conf import settings +from django.db import migrations +from iam.contrib.iam_migration.migrator import IAMMigrator + + +def forward_func(apps, schema_editor): + + migrator = IAMMigrator(Migration.migration_json) + migrator.migrate() + + +class Migration(migrations.Migration): + migration_json = "0006_action_groups.json" + + dependencies = [('bcs_iam_migration', '0005_bk_bcs_app_202108181525')] + + operations = [migrations.RunPython(forward_func)] diff --git a/bcs-app/backend/iam/bcs_iam_migration/migrations/0007_bk_bcs_app_202108262047.py b/bcs-app/backend/iam/bcs_iam_migration/migrations/0007_bk_bcs_app_202108262047.py new file mode 100644 index 000000000..23ad50b44 --- /dev/null +++ b/bcs-app/backend/iam/bcs_iam_migration/migrations/0007_bk_bcs_app_202108262047.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import codecs +import json +import os + +from django.conf import settings +from django.db import migrations +from iam.contrib.iam_migration.migrator import IAMMigrator + + +def forward_func(apps, schema_editor): + + migrator = IAMMigrator(Migration.migration_json) + migrator.migrate() + + +class Migration(migrations.Migration): + migration_json = "0007_resource_creator_actions.json" + + dependencies = [('bcs_iam_migration', '0006_bk_bcs_app_202108181525')] + + operations = [migrations.RunPython(forward_func)] diff --git a/bcs-app/backend/iam/bcs_iam_migration/migrations/__init__.py b/bcs-app/backend/iam/bcs_iam_migration/migrations/__init__.py new file mode 100644 index 000000000..08f8f6ab8 --- /dev/null +++ b/bcs-app/backend/iam/bcs_iam_migration/migrations/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" diff --git a/bcs-app/backend/bcs_web/iam/permissions.py b/bcs-app/backend/iam/legacy_perms.py similarity index 97% rename from bcs-app/backend/bcs_web/iam/permissions.py rename to bcs-app/backend/iam/legacy_perms.py index 1d39bfb39..d5c9833c2 100644 --- a/bcs-app/backend/bcs_web/iam/permissions.py +++ b/bcs-app/backend/iam/legacy_perms.py @@ -15,15 +15,12 @@ from typing import Dict, Optional from django.conf import settings +from iam import IAM, OP, Action, MultiActionRequest, Request, Resource, Subject +from iam.api.client import Client +from iam.api.http import http_get, http_post +from iam.apply import models +from iam.exceptions import AuthAPIError, AuthInvalidRequest -try: - from iam import IAM, OP, Action, MultiActionRequest, Request, Resource, Subject - from iam.api.client import Client - from iam.api.http import http_get, http_post - from iam.apply import models - from iam.exceptions import AuthAPIError, AuthInvalidRequest -except Exception: - pass from backend.utils.basic import ChoicesEnum from backend.utils.exceptions import PermissionDeniedError diff --git a/bcs-app/backend/iam/open_apis/__init__.py b/bcs-app/backend/iam/open_apis/__init__.py new file mode 100644 index 000000000..08f8f6ab8 --- /dev/null +++ b/bcs-app/backend/iam/open_apis/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" diff --git a/bcs-app/backend/bcs_web/iam/open_apis/authentication.py b/bcs-app/backend/iam/open_apis/authentication.py similarity index 85% rename from bcs-app/backend/bcs_web/iam/open_apis/authentication.py rename to bcs-app/backend/iam/open_apis/authentication.py index 363bbcc51..be7d78cdf 100644 --- a/bcs-app/backend/bcs_web/iam/open_apis/authentication.py +++ b/bcs-app/backend/iam/open_apis/authentication.py @@ -13,29 +13,28 @@ specific language governing permissions and limitations under the License. """ from django.conf import settings -from rest_framework import exceptions +from iam import IAM from rest_framework.authentication import BasicAuthentication +from rest_framework.exceptions import AuthenticationFailed as RESTAuthenticationFailed -try: - from iam import IAM -except Exception: - pass from backend.utils import FancyDict from .exceptions import AuthenticationFailed class IamBasicAuthentication(BasicAuthentication): + """自定义认证逻辑, 对权限中心请求认证""" + def authenticate(self, request): try: result = super().authenticate(request) if result is None: raise AuthenticationFailed("basic auth failed") - except exceptions.AuthenticationFailed as e: + except RESTAuthenticationFailed as e: raise AuthenticationFailed(str(e)) return result - def authenticate_credentials(self, userid, password): + def authenticate_credentials(self, userid: str, password: str, request=None): if userid != "bk_iam": raise AuthenticationFailed("username is not bk_iam") diff --git a/bcs-app/backend/bcs_web/iam/open_apis/constants.py b/bcs-app/backend/iam/open_apis/constants.py similarity index 57% rename from bcs-app/backend/bcs_web/iam/open_apis/constants.py rename to bcs-app/backend/iam/open_apis/constants.py index fc280e557..73e43586e 100644 --- a/bcs-app/backend/bcs_web/iam/open_apis/constants.py +++ b/bcs-app/backend/iam/open_apis/constants.py @@ -12,20 +12,20 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -from backend.utils.basic import ChoicesEnum +from backend.iam.permissions.resources import constants +from backend.packages.blue_krill.data_types.enum import StructuredEnum +ResourceType = constants.ResourceType -class MethodChoices(ChoicesEnum): - LIST_ATTR = "list_attr" - LIST_ATTR_VALUE = "list_attr_value" - LIST_INSTANCE = "list_instance" - FETCH_INSTANCE_INFO = "fetch_instance_info" - LIST_INSTANCE_BY_POLICY = "list_instance_by_policy" - _choices_labels = ( - (LIST_ATTR, "list_attr"), - (LIST_ATTR_VALUE, "list_attr_value"), - (LIST_INSTANCE, "list_instance"), - (FETCH_INSTANCE_INFO, "fetch_instance_info"), - (LIST_INSTANCE_BY_POLICY, "list_instance_by_policy"), - ) +class MethodType(str, StructuredEnum): + """ + 权限中心拉取资源的 method 参数值 + 字段协议说明 https://bk.tencent.com/docs/document/6.0/160/8427?r=1 + """ + + LIST_ATTR = 'list_attr' + LIST_ATTR_VALUE = 'list_attr_value' + LIST_INSTANCE = 'list_instance' + FETCH_INSTANCE_INFO = 'fetch_instance_info' + LIST_INSTANCE_BY_POLICY = 'list_instance_by_policy' diff --git a/bcs-app/backend/bcs_web/iam/open_apis/exceptions.py b/bcs-app/backend/iam/open_apis/exceptions.py similarity index 94% rename from bcs-app/backend/bcs_web/iam/open_apis/exceptions.py rename to bcs-app/backend/iam/open_apis/exceptions.py index 2b87587b1..027d9e902 100644 --- a/bcs-app/backend/bcs_web/iam/open_apis/exceptions.py +++ b/bcs-app/backend/iam/open_apis/exceptions.py @@ -17,6 +17,3 @@ class AuthenticationFailed(exceptions.APIError): code = 401 - - -ResNotFoundError = exceptions.ResNotFoundError diff --git a/bcs-app/backend/iam/open_apis/provider/__init__.py b/bcs-app/backend/iam/open_apis/provider/__init__.py new file mode 100644 index 000000000..08f8f6ab8 --- /dev/null +++ b/bcs-app/backend/iam/open_apis/provider/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" diff --git a/bcs-app/backend/iam/open_apis/provider/namespace.py b/bcs-app/backend/iam/open_apis/provider/namespace.py new file mode 100644 index 000000000..82d113602 --- /dev/null +++ b/bcs-app/backend/iam/open_apis/provider/namespace.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import Dict, List + +from iam.collection import FancyDict +from iam.resource.provider import ListResult, ResourceProvider +from iam.resource.utils import Page + +from backend.components.base import ComponentAuth +from backend.components.paas_cc import PaaSCCClient + +from .utils import get_system_token + + +class NamespaceProvider(ResourceProvider): + """命名空间 Provider""" + + def list_instance(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult: + cluster_id = filter_obj.parent['id'] + namespace_list = self._list_namespaces(cluster_id) + + namespace_slice = namespace_list[page_obj.slice_from : page_obj.slice_to] + results = [{'id': f"{cluster_id}:{ns['name']}", 'display_name': ns['name']} for ns in namespace_slice] + + return ListResult(results=results, count=len(namespace_list)) + + def fetch_instance_info(self, filter_obj: FancyDict, **options) -> ListResult: + cluster_id = filter_obj.parent['id'] + namespace_list = self._list_namespaces(cluster_id) + + if filter_obj.ids: + # cluster_ns_id 结构如 BCS-K8S-40000:test + filter_ns_list = [cluster_ns_id.split(':')[1] for cluster_ns_id in filter_obj.ids] + results = [ + {'id': f"{cluster_id}:{ns['name']}", 'display_name': ns['name']} + for ns in namespace_list + if ns['name'] in filter_ns_list + ] + else: + results = [{'id': f"{cluster_id}:{ns['name']}", 'display_name': ns['name']} for ns in namespace_list] + + return ListResult(results=results, count=len(results)) + + def list_instance_by_policy(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult: + # TODO 确认基于实例的查询是不是就是id的过滤查询 + return ListResult(results=[], count=0) + + def list_attr(self, **options) -> ListResult: + return ListResult(results=[], count=0) + + def list_attr_value(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult: + return ListResult(results=[], count=0) + + def _list_namespaces(self, cluster_id: str) -> List[Dict]: + paas_cc = PaaSCCClient(auth=ComponentAuth(get_system_token())) + cluster = paas_cc.get_cluster_by_id(cluster_id=cluster_id) + ns_data = paas_cc.get_cluster_namespace_list(project_id=cluster['project_id'], cluster_id=cluster_id) + return ns_data['results'] diff --git a/bcs-app/backend/iam/open_apis/provider/project.py b/bcs-app/backend/iam/open_apis/provider/project.py new file mode 100644 index 000000000..7951a7a00 --- /dev/null +++ b/bcs-app/backend/iam/open_apis/provider/project.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from iam.collection import FancyDict +from iam.resource.provider import ListResult, ResourceProvider +from iam.resource.utils import Page + +from backend.container_service.projects.base import list_projects + +from .utils import get_system_token + + +class ProjectProvider(ResourceProvider): + """项目资源的 Provider""" + + def list_instance(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult: + projects = list_projects(get_system_token()) + projects_slice = projects[page_obj.slice_from : page_obj.slice_to] + results = [{'id': p['project_id'], 'display_name': p['project_name']} for p in projects_slice] + return ListResult(results=results, count=len(projects)) + + def fetch_instance_info(self, filter_obj: FancyDict, **options) -> ListResult: + query_params = {'project_ids': ','.join(filter_obj.ids)} + projects = list_projects(get_system_token(), query_params) + results = [{'id': p['project_id'], 'display_name': p['project_name']} for p in projects] + return ListResult(results=results, count=len(results)) + + def list_instance_by_policy(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult: + # TODO 确认基于实例的查询是不是就是id的过滤查询 + return ListResult(results=[], count=0) + + def list_attr(self, **options) -> ListResult: + return ListResult(results=[], count=0) + + def list_attr_value(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult: + return ListResult(results=[], count=0) diff --git a/bcs-app/backend/iam/open_apis/provider/resource.py b/bcs-app/backend/iam/open_apis/provider/resource.py new file mode 100644 index 000000000..703f5785a --- /dev/null +++ b/bcs-app/backend/iam/open_apis/provider/resource.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import Dict, List, Optional, Union + +from iam.collection import FancyDict +from iam.resource.utils import Page, get_filter_obj, get_page_obj + +from ..constants import ResourceType +from .namespace import NamespaceProvider +from .project import ProjectProvider + +PROVIDER_CLS_MAP = {ResourceType.Project: ProjectProvider, ResourceType.Namespace: NamespaceProvider} + + +class ResourceProvider: + def __init__(self, resource_type: str): + """:param resource_type: 资源类型 如 project, cluster 等""" + self.resource_provider = PROVIDER_CLS_MAP[resource_type]() + + def provide(self, method: str, data: Dict, **options) -> Union[List, Dict]: + """ + 根据 method 值, 调用对应的方法返回数据 + :param method: 值包括 list_attr, list_attr_value, list_instance 等 + :param data: 其他查询条件数据,如分页数据等 + """ + handler = getattr(self, method) + return handler(data, **options) + + def list_attr(self, data: Optional[Dict] = None, **options) -> List[Dict]: + """ + 查询某个资源类型可用于配置权限的属性列表 + :param data: 占位字段,为了上面provide方法的处理统一 + """ + result = self.resource_provider.list_attr(**options) + return result.to_list() + + def list_attr_value(self, data: Dict, **options) -> Dict: + """获取一个资源类型某个属性的值列表""" + filter_obj, page_obj = self._parse_filter_and_page(data) + result = self.resource_provider.list_attr_value(filter_obj, page_obj, **options) + return result.to_dict() + + def list_instance(self, data: Dict, **options) -> Dict: + """查询资源实例列表(支持分页)""" + filter_obj, page_obj = self._parse_filter_and_page(data) + result = self.resource_provider.list_instance(filter_obj, page_obj, **options) + return result.to_dict() + + def fetch_instance_info(self, data: Dict, **options) -> List[Dict]: + """查询资源实例列表""" + filter_obj, _ = self._parse_filter_and_page(data) + result = self.resource_provider.fetch_instance_info(filter_obj, **options) + return result.to_list() + + def list_instance_by_policy(self, data: Dict, **options) -> List[Dict]: + """根据策略表达式查询资源实例""" + filter_obj, page_obj = self._parse_filter_and_page(data) + result = self.resource_provider.list_instance_by_policy(filter_obj, page_obj, **options) + return result.to_list() + + def _parse_filter_and_page(self, data: Dict) -> (FancyDict, Page): + filter_obj = get_filter_obj(data["filter"], ["ids", "parent", "search", "resource_type_chain"]) + page_obj = get_page_obj(data.get("page")) + return filter_obj, page_obj diff --git a/bcs-app/backend/iam/open_apis/provider/utils.py b/bcs-app/backend/iam/open_apis/provider/utils.py new file mode 100644 index 000000000..3fd7664ce --- /dev/null +++ b/bcs-app/backend/iam/open_apis/provider/utils.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from backend.components import ssm + + +def get_system_token(): + """获取非用户 access_token""" + return ssm.get_client_access_token()["access_token"] diff --git a/bcs-app/backend/bcs_web/iam/open_apis/serializers.py b/bcs-app/backend/iam/open_apis/serializers.py similarity index 74% rename from bcs-app/backend/bcs_web/iam/open_apis/serializers.py rename to bcs-app/backend/iam/open_apis/serializers.py index 33ef4cbb2..82b12b1ba 100644 --- a/bcs-app/backend/bcs_web/iam/open_apis/serializers.py +++ b/bcs-app/backend/iam/open_apis/serializers.py @@ -14,11 +14,11 @@ """ from rest_framework import serializers -from .constants import MethodChoices +from .constants import MethodType, ResourceType class QueryResourceSLZ(serializers.Serializer): - method = serializers.ChoiceField(choices=MethodChoices.get_choices()) - type = serializers.CharField() - filter = serializers.JSONField(default={}) - page = serializers.JSONField(default={}) + method = serializers.ChoiceField(choices=MethodType.get_choices()) + type = serializers.ChoiceField(choices=ResourceType.get_choices()) + filter = serializers.JSONField(default=dict) + page = serializers.JSONField(default=dict) diff --git a/bcs-app/backend/bcs_web/iam/open_apis/urls.py b/bcs-app/backend/iam/open_apis/urls.py similarity index 91% rename from bcs-app/backend/bcs_web/iam/open_apis/urls.py rename to bcs-app/backend/iam/open_apis/urls.py index 2dde8d822..fc94015a6 100644 --- a/bcs-app/backend/bcs_web/iam/open_apis/urls.py +++ b/bcs-app/backend/iam/open_apis/urls.py @@ -14,4 +14,4 @@ """ from django.conf.urls import include, url -urlpatterns = [url(r"^v1/", include("backend.bcs_web.iam.open_apis.v1.urls"))] +urlpatterns = [url(r"^v1/", include("backend.iam.open_apis.v1.urls"))] diff --git a/bcs-app/backend/iam/open_apis/v1/__init__.py b/bcs-app/backend/iam/open_apis/v1/__init__.py new file mode 100644 index 000000000..08f8f6ab8 --- /dev/null +++ b/bcs-app/backend/iam/open_apis/v1/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" diff --git a/bcs-app/backend/iam/open_apis/v1/urls.py b/bcs-app/backend/iam/open_apis/v1/urls.py new file mode 100644 index 000000000..fe206cb59 --- /dev/null +++ b/bcs-app/backend/iam/open_apis/v1/urls.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from django.conf.urls import url + +from .. import views + +resource_types = 'projects|namespaces' + +urlpatterns = [ + url(r'^(?P{})/'.format(resource_types), views.ResourceAPIView.as_view()), +] diff --git a/bcs-app/backend/bcs_web/iam/open_apis/views.py b/bcs-app/backend/iam/open_apis/views.py similarity index 82% rename from bcs-app/backend/bcs_web/iam/open_apis/views.py rename to bcs-app/backend/iam/open_apis/views.py index 7fc1d38de..59e84665f 100644 --- a/bcs-app/backend/bcs_web/iam/open_apis/views.py +++ b/bcs-app/backend/iam/open_apis/views.py @@ -18,11 +18,13 @@ from backend.utils.renderers import BKAPIRenderer from .authentication import IamBasicAuthentication -from .resources.provider import BCSResourceProvider +from .provider.resource import ResourceProvider from .serializers import QueryResourceSLZ class ResourceAPIView(APIView): + """统一入口: 提供给权限中心拉取各类资源""" + renderer_classes = (BKAPIRenderer,) authentication_classes = (IamBasicAuthentication,) permission_classes = () @@ -35,11 +37,11 @@ def _get_options(self, request): request.LANGUAGE_CODE = "en" return {"language": language} - def post(self, request): + def post(self, request, *args, **kwargs): serializer = QueryResourceSLZ(data=request.data) serializer.is_valid(raise_exception=True) validated_data = serializer.validated_data - provider = BCSResourceProvider(resource_type=validated_data["type"]) - resp = provider.provide(validated_data, **self._get_options(request)) + provider = ResourceProvider(resource_type=validated_data["type"]) + resp = provider.provide(method=validated_data['method'], data=validated_data, **self._get_options(request)) return Response(resp) diff --git a/bcs-app/backend/iam/permissions/__init__.py b/bcs-app/backend/iam/permissions/__init__.py new file mode 100644 index 000000000..08f8f6ab8 --- /dev/null +++ b/bcs-app/backend/iam/permissions/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" diff --git a/bcs-app/backend/iam/permissions/apply_url.py b/bcs-app/backend/iam/permissions/apply_url.py new file mode 100644 index 000000000..44d63f6e7 --- /dev/null +++ b/bcs-app/backend/iam/permissions/apply_url.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import logging +from typing import List + +from django.conf import settings +from iam import IAM +from iam.apply import models + +from .request import ActionResourcesRequest + +logger = logging.getLogger(__name__) + + +class ApplyURLGenerator: + iam = IAM(settings.APP_ID, settings.APP_TOKEN, settings.BK_IAM_HOST, settings.BK_PAAS_INNER_HOST) + + @classmethod + def generate_apply_url(cls, username: str, action_request_list: List[ActionResourcesRequest]) -> str: + """ + 生成权限申请跳转 url + 参考 https://github.com/TencentBlueKing/iam-python-sdk/blob/master/docs/usage.md#14-获取无权限申请跳转url + """ + app = cls._make_application(action_request_list) + ok, _, url = cls.iam.get_apply_url(app, bk_username=username) + if not ok: + return settings.BK_IAM_APP_URL + return url + + @staticmethod + def _make_application(action_request_list: List[ActionResourcesRequest]) -> models.Application: + """为 generate_apply_url 方法生成 models.Application""" + return models.Application(settings.APP_ID, actions=[req.to_action() for req in action_request_list]) diff --git a/bcs-app/backend/iam/permissions/client.py b/bcs-app/backend/iam/permissions/client.py new file mode 100644 index 000000000..af2d29f44 --- /dev/null +++ b/bcs-app/backend/iam/permissions/client.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import Dict, List, Optional + +from django.conf import settings +from iam import IAM, Action, MultiActionRequest, Request, Resource, Subject + +from .request import ResourceRequest + + +class IAMClient: + """提供基础的 iam client 方法封装""" + + iam = IAM(settings.APP_ID, settings.APP_TOKEN, settings.BK_IAM_HOST, settings.BK_PAAS_INNER_HOST) + + def resource_type_allowed(self, username: str, action_id: str, use_cache: bool = False) -> bool: + """ + 判断用户是否具备某个操作的权限 + note: 权限判断与资源实例无关,如创建某资源 + """ + request = self._make_request(username, action_id) + if not use_cache: + return self.iam.is_allowed(request) + return self.iam.is_allowed_with_cache(request) + + def resource_inst_allowed( + self, username: str, action_id: str, res_request: ResourceRequest, use_cache: bool = False + ) -> bool: + """ + 判断用户对某个资源实例是否具有指定操作的权限 + note: 权限判断与资源实例有关,如更新某个具体资源 + """ + request = self._make_request(username, action_id, resources=res_request.make_resources()) + if not use_cache: + return self.iam.is_allowed(request) + return self.iam.is_allowed_with_cache(request) + + def resource_type_multi_actions_allowed(self, username: str, action_ids: List[str]) -> Dict[str, bool]: + """ + 判断用户是否具备多个操作的权限 + note: 权限判断与资源实例无关,如创建某资源 + + :returns 示例 {'project_create': True} + """ + return {action_id: self.resource_type_allowed(username, action_id) for action_id in action_ids} + + def resource_inst_multi_actions_allowed( + self, username: str, action_ids: List[str], res_request: ResourceRequest + ) -> Dict[str, bool]: + """ + 判断用户对某个资源实例是否具有多个操作的权限. + note: 权限判断与资源实例有关,如更新某个具体资源 + + :return 示例 {'project_view': True, 'project_edit': False} + """ + actions = [Action(action_id) for action_id in action_ids] + request = MultiActionRequest( + settings.APP_ID, Subject("user", username), actions, res_request.make_resources(), None + ) + return self.iam.resource_multi_actions_allowed(request) + + def batch_resource_multi_actions_allowed( + self, username: str, action_ids: List[str], res_request: ResourceRequest + ) -> Dict[str, Dict[str, bool]]: + """ + 判断用户对某些资源是否具有多个指定操作的权限 + note: 当前sdk仅支持同类型的资源 + + :return 示例 {'0ad86c25363f4ef8adcb7ac67a483837': {'project_view': True, 'project_edit': False}} + """ + actions = [Action(action_id) for action_id in action_ids] + request = MultiActionRequest(settings.APP_ID, Subject("user", username), actions, [], None) + resources_list = [[res] for res in res_request.make_resources()] + return self.iam.batch_resource_multi_actions_allowed(request, resources_list) + + def _make_request(self, username: str, action_id: str, resources: Optional[List[Resource]] = None) -> Request: + return Request( + settings.APP_ID, + Subject("user", username), + Action(action_id), + resources, + None, + ) + + def _grant_resource_creator_actions(self, username: str, data: Dict): + """ + 用于创建资源时,注册用户对该资源的关联操作权限. + note: 具体的关联操作见权限模型的 resource_creator_actions 字段 + """ + return self.iam._client.grant_resource_creator_actions(None, username, data) diff --git a/bcs-app/backend/iam/permissions/decorators.py b/bcs-app/backend/iam/permissions/decorators.py new file mode 100644 index 000000000..4d0d34361 --- /dev/null +++ b/bcs-app/backend/iam/permissions/decorators.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import importlib +import logging +from abc import ABCMeta, abstractmethod + +import wrapt + +from .exceptions import PermissionDeniedError +from .perm import PermCtx +from .perm import Permission as PermPermission + +logger = logging.getLogger(__name__) + + +class RelatedPermission(metaclass=ABCMeta): + """ + 用于资源 Permission 类的方法装饰, 目的是支持 related_actions. + + 如 related_project_perm 和 related_cluster_perm 装饰器的用法: + + class ClusterPermission(Permission): + + resource_type: str = 'cluster' + resource_request_cls: Type[ResourceRequest] = ClusterRequest + + @related_project_perm(method_name='can_view') + def can_view(self, perm_ctx: ClusterPermCtx, raise_exception: bool = True, just_raise: bool = False) -> bool: + return self.can_action(perm_ctx, ClusterAction.VIEW, raise_exception, just_raise) + + @related_cluster_perm(method_name='can_view') + def can_manage(self, perm_ctx: ClusterPermCtx, raise_exception: bool = True, just_raise: bool = False) -> bool: + return self.can_action(perm_ctx, ClusterAction.MANAGE, raise_exception, just_raise) + + """ + + module_name: str # 资源模块名 如 cluster, project + + def __init__(self, method_name: str): + """ + :param method_name: 权限类的 can_{action} 方法名,用于校验用户是否具有对应的操作权限 + """ + self.method_name = method_name + + def _gen_perm_obj(self) -> PermPermission: + """获取权限类实例,如 project.ProjectPermission""" + p_module_name = __name__[: __name__.rfind(".")] + try: + return getattr( + importlib.import_module(f'{p_module_name}.resources.{self.module_name}'), + f'{self.module_name.capitalize()}Permission', + )() + except (ModuleNotFoundError, AttributeError) as e: + logger.error('_gen_perm_obj error: %s', e) + + @wrapt.decorator + def __call__(self, wrapped, instance, args, kwargs): + self.perm_obj = self._gen_perm_obj() + + perm_ctx = self._convert_perm_ctx(instance, args, kwargs) + + try: + is_allowed = wrapped(*args, **kwargs) + except PermissionDeniedError as e: + # 按照权限中心的建议,无论关联资源操作是否有权限,统一按照无权限返回,目的是生成最终的 apply_url + perm_ctx.force_raise = True + try: + getattr(self.perm_obj, self.method_name)(perm_ctx) + except PermissionDeniedError as err: + raise PermissionDeniedError( + f'{e.message}; {err.message}', + username=perm_ctx.username, + action_request_list=e.action_request_list + err.action_request_list, + ) + else: + # 无权限,并且没有抛出 PermissionDeniedError, 说明 raise_exception = False + if not is_allowed: + return is_allowed + + logger.debug(f'continue to verify {self.method_name} {self.module_name} permission...') + + # 有权限时,继续校验关联操作的权限 + raise_exception = kwargs.get('raise_exception', True) + return getattr(self.perm_obj, self.method_name)(perm_ctx, raise_exception=raise_exception) + + @abstractmethod + def _convert_perm_ctx(self, instance, args, kwargs) -> PermCtx: + """将被装饰的方法中的 perm_ctx 转换成 perm_obj.method_name 需要的 perm_ctx""" + + @property + def action_id(self) -> str: + return f'{self.perm_obj.resource_type}_{self.method_name[4:]}' + + +class Permission: + """鉴权装饰器基类,用于装饰函数或者方法""" + + module_name: str # 资源模块名 如 cluster, project + + def __init__(self, method_name: str): + """ + :param method_name: 权限类的 can_{action} 方法名,用于校验用户是否具有对应的操作权限 + """ + self.method_name = method_name + + def _gen_perm_obj(self) -> PermPermission: + """获取权限类实例,如 project.ProjectPermission""" + p_module_name = __name__[: __name__.rfind(".")] + try: + return getattr( + importlib.import_module(f'{p_module_name}.resources.{self.module_name}'), + f'{self.module_name.capitalize()}Permission', + )() + except (ModuleNotFoundError, AttributeError) as e: + logger.error('_gen_perm_obj error: %s', e) + + @wrapt.decorator + def __call__(self, wrapped, instance, args, kwargs): + + self.perm_obj = self._gen_perm_obj() + + if len(args) <= 0: + raise TypeError('missing PermCtx instance argument') + if not isinstance(args[0], PermCtx): + raise TypeError('missing ProjectPermCtx instance argument') + + getattr(self.perm_obj, self.method_name)(args[0]) + + return wrapped(*args, **kwargs) diff --git a/bcs-app/backend/iam/permissions/exceptions.py b/bcs-app/backend/iam/permissions/exceptions.py new file mode 100644 index 000000000..bafe684a5 --- /dev/null +++ b/bcs-app/backend/iam/permissions/exceptions.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import Dict, List, Optional + +from .apply_url import ApplyURLGenerator +from .request import ActionResourcesRequest + + +class PermissionDeniedError(Exception): + message = 'Permission denied' + code = 40300 + + def __init__( + self, + message: str = '', + username: str = '', + action_request_list: Optional[List[ActionResourcesRequest]] = None, + ): + """ + :param message: 异常信息 + :param username: 无权限的用户名, 用于生成 apply_url + :param action_request_list: 用于生成 apply_url + """ + + if message: + self.message = message + + self.username = username + self.action_request_list = action_request_list + + @property + def data(self) -> Dict: + return { + 'apply_url': ApplyURLGenerator.generate_apply_url(self.username, self.action_request_list), + 'action_list': [ + {'resource_type': action_request.resource_type, 'action_id': action_request.action_id} + for action_request in self.action_request_list + ], + } + + def __str__(self): + return f'{self.code}: {self.message}' + + +class AttrValidationError(Exception): + """属性字段校验异常""" diff --git a/bcs-app/backend/iam/permissions/perm.py b/bcs-app/backend/iam/permissions/perm.py new file mode 100644 index 000000000..16150838a --- /dev/null +++ b/bcs-app/backend/iam/permissions/perm.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import logging +from abc import ABC, abstractmethod +from typing import List, Optional, Type + +import attr +from django.conf import settings + +from .client import IAMClient +from .exceptions import AttrValidationError, PermissionDeniedError +from .request import ActionResourcesRequest, IAMResource, ResourceRequest + +logger = logging.getLogger(__name__) + + +@attr.dataclass +class PermCtx: + """ + 权限参数上下文 + note: 由于 force_raise 默认值的原因,其子类属性必须设置默认值 + """ + + username: str + force_raise: bool = False # 如果为 True, 表示不做权限校验,直接以无权限方式抛出异常 + + def validate_resource_id(self): + """校验资源实例 ID. 如果校验不过,抛出 AttrValidationError 异常""" + if not self.resource_id: + raise AttrValidationError(f'missing valid resource_id') + + @property + def resource_id(self) -> str: + return '' + + +class Permission(ABC, IAMClient): + """ + 对接 IAM 的权限基类 + """ + + resource_type: str = '' + resource_request_cls: Type[ResourceRequest] = ResourceRequest + parent_res_perm: Optional['Permission'] = None # 父级资源的权限类对象 + + def can_action_with_view( + self, perm_ctx: PermCtx, action_id: str, view_action_id: str, raise_exception: bool, use_cache: bool = False + ) -> bool: + """ + 校验用户的 action_id 权限时,级联校验对资源的查看(view_action_id)权限 + + :param perm_ctx: 权限校验的上下文 + :param action_id: 资源操作 ID + :param raise_exception: 无权限时,是否抛出异常 + :param use_cache: 是否使用本地缓存 (缓存时间 1 min) 校验权限。用于非敏感操作鉴权,比如 view 操作 + """ + if action_id == view_action_id: + raise ValueError('parameter action_id and view_action_id are equal') + + if not view_action_id.endswith('_view'): + raise ValueError("parameter view_action_id must ends with '_view'") + + try: + is_allowed = self.can_action(perm_ctx, action_id, raise_exception, use_cache) + except PermissionDeniedError as e: + # 按照权限中心的建议,无论关联资源操作是否有权限,统一按照无权限返回,目的是生成最终的 apply_url + perm_ctx.force_raise = True + try: + self.can_action(perm_ctx, view_action_id, raise_exception, use_cache) + except PermissionDeniedError as err: + raise PermissionDeniedError( + f'{e.message}; {err.message}', + username=perm_ctx.username, + action_request_list=e.action_request_list + err.action_request_list, + ) + else: + # action_id 无权限,并且没有抛出 PermissionDeniedError, 说明 raise_exception = False + if not is_allowed: + return is_allowed + # action_id 有权限时,继续校验 view_action_id 权限 + logger.debug(f'continue to verify {view_action_id} permission...') + return self.can_action(perm_ctx, view_action_id, raise_exception, use_cache) + + def can_action(self, perm_ctx: PermCtx, action_id: str, raise_exception: bool, use_cache: bool = False) -> bool: + """ + 校验用户的 action_id 权限 + + :param perm_ctx: 权限校验的上下文 + :param action_id: 资源操作 ID + :param raise_exception: 无权限时,是否抛出异常 + :param use_cache: 是否使用本地缓存 (缓存时间 1 min) 校验权限。用于非敏感操作鉴权,比如 view 操作 + """ + if perm_ctx.force_raise: + self._raise_permission_denied_error(perm_ctx, action_id) + + is_allowed = self._can_action(perm_ctx, action_id, use_cache) + + if raise_exception and not is_allowed: + self._raise_permission_denied_error(perm_ctx, action_id) + + return is_allowed + + def grant_resource_creator_actions(self, username: str, resource_id: str, resource_name: str): + """ + 用于创建资源时,注册用户对该资源的关联操作权限. + note: 具体的关联操作见权限模型的 resource_creator_actions 字段 + TODO 需要针对层级资源重构 + """ + data = { + "type": self.resource_type, + "id": resource_id, + "name": resource_name, + "system": settings.APP_ID, + "creator": username, + } + return self.iam._client.grant_resource_creator_actions(None, username, data) + + def make_res_request(self, res_id: str, perm_ctx: PermCtx) -> ResourceRequest: + """创建当前资源 request""" + return self.resource_request_cls(res_id) + + def has_parent_resource(self) -> bool: + return self.parent_res_perm is not None + + @abstractmethod + def get_parent_chain(self, perm_ctx: PermCtx) -> List[IAMResource]: + """从 ctx 中获取 parent_chain""" + + def _can_action(self, perm_ctx: PermCtx, action_id: str, use_cache: bool = False) -> bool: + res_id = self.get_resource_id(perm_ctx) + + if res_id: # 与当前资源实例相关 + res_request = self.make_res_request(res_id, perm_ctx) + return self.resource_inst_allowed(perm_ctx.username, action_id, res_request, use_cache) + + # 与当前资源实例无关, 并且无关联上级资源, 按资源实例无关处理 + if not self.has_parent_resource(): + return self.resource_type_allowed(perm_ctx.username, action_id, use_cache) + + # 有关联上级资源 + res_request = self.parent_res_perm.make_res_request( + res_id=self.parent_res_perm.get_resource_id(perm_ctx), perm_ctx=perm_ctx + ) + return self.resource_inst_allowed(perm_ctx.username, action_id, res_request, use_cache) + + def _raise_permission_denied_error(self, perm_ctx: PermCtx, action_id: str): + res_id = self.get_resource_id(perm_ctx) + resources = None + resource_type = self.resource_type + parent_chain = None + + if res_id: + resources = [res_id] + parent_chain = self.get_parent_chain(perm_ctx) + elif self.has_parent_resource(): + resource_type = self.parent_res_perm.resource_type + resources = [self.parent_res_perm.get_resource_id(perm_ctx)] + parent_chain = self.parent_res_perm.get_parent_chain(perm_ctx) + + raise PermissionDeniedError( + f"no {action_id} permission", + username=perm_ctx.username, + action_request_list=[ActionResourcesRequest(action_id, resource_type, resources, parent_chain)], + ) + + @abstractmethod + def get_resource_id(self, perm_ctx: PermCtx) -> Optional[str]: + """从 ctx 中获取当前资源对应的 id""" diff --git a/bcs-app/backend/iam/permissions/request.py b/bcs-app/backend/iam/permissions/request.py new file mode 100644 index 000000000..9a4382bbb --- /dev/null +++ b/bcs-app/backend/iam/permissions/request.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from abc import ABC +from collections import namedtuple +from typing import Dict, List, Optional, Union + +from django.conf import settings +from iam import Resource +from iam.apply import models + + +class ResourceRequest(ABC): + resource_type: str = '' + attr: Optional[Dict] = None + + def __init__(self, res: Union[List[str], str], **attr_kwargs): + """ + :param res: 单个资源 ID 或资源 ID 列表 + :param attr_kwargs: 用于替换 attr 中可能需要 format 的值 + """ + self.res = [str(res_id) for res_id in res] if isinstance(res, list) else str(res) + self.attr_kwargs = dict(**attr_kwargs) + self._validate_attr_kwargs() + + def make_resources(self) -> List[Resource]: + if isinstance(self.res, str): + return [Resource(settings.APP_ID, self.resource_type, self.res, self._make_attribute(self.res))] + + return [ + Resource(settings.APP_ID, self.resource_type, res_id, self._make_attribute(res_id)) for res_id in self.res + ] + + def _validate_attr_kwargs(self): + """如果校验不通过,抛出 AttrValidateError 异常""" + return + + def _make_attribute(self, res_id: str) -> Dict: + return {} + + +IAMResource = namedtuple('IAMResource', 'resource_type resource_id') + + +class ActionResourcesRequest: + """ + 操作资源请求. + note: resources 是由资源 ID 构成的列表. 为 None 时,表示资源无关. + 资源实例相关时,resources 表示的资源必须具有相同的父实例。以命名空间为例,它们必须是同项目同集群下 + """ + + def __init__( + self, + action_id: str, + resource_type: Optional[str] = None, + resources: Optional[List[str]] = None, + parent_chain: List[IAMResource] = None, + ): + """ + :param action_id: 操作 ID + :param resource_type: 资源类型 + :param resources: 资源 ID 列表 + :param parent_chain: 按照父类层级排序(父->子) [(resource_type, resource_id), ] + """ + self.action_id = action_id + self.resource_type = resource_type + self.resources = resources + self.parent_chain = parent_chain + + def to_action(self) -> Union[models.ActionWithResources, models.ActionWithoutResources]: + # 资源实例相关 + if self.resources: + parent_chain_node = self._to_parent_chain_node() + instances = [ + models.ResourceInstance(parent_chain_node + [models.ResourceNode(self.resource_type, res_id, res_id)]) + for res_id in self.resources + ] + related_resource_type = models.RelatedResourceType(settings.APP_ID, self.resource_type, instances) + return models.ActionWithResources(self.action_id, [related_resource_type]) + + # 资源实例无关 + return models.ActionWithoutResources(self.action_id) + + def _to_parent_chain_node(self) -> List[models.ResourceNode]: + if self.parent_chain: + return [models.ResourceNode(p.resource_type, p.resource_id, p.resource_id) for p in self.parent_chain] + return [] diff --git a/bcs-app/backend/iam/permissions/resources/__init__.py b/bcs-app/backend/iam/permissions/resources/__init__.py new file mode 100644 index 000000000..08f8f6ab8 --- /dev/null +++ b/bcs-app/backend/iam/permissions/resources/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" diff --git a/bcs-app/backend/iam/permissions/resources/cluster.py b/bcs-app/backend/iam/permissions/resources/cluster.py new file mode 100644 index 000000000..5df347c4f --- /dev/null +++ b/bcs-app/backend/iam/permissions/resources/cluster.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import Dict, List, Optional, Type + +import attr + +from backend.iam.permissions import decorators +from backend.iam.permissions.exceptions import AttrValidationError +from backend.iam.permissions.perm import PermCtx, Permission, ResourceRequest +from backend.iam.permissions.request import IAMResource +from backend.packages.blue_krill.data_types.enum import EnumField, StructuredEnum + +from .constants import ResourceType +from .project import ProjectPermission, related_project_perm + + +class ClusterAction(str, StructuredEnum): + CREATE = EnumField('cluster_create', label='cluster_create') + VIEW = EnumField('cluster_view', label='cluster_view') + MANAGE = EnumField('cluster_manage', label='cluster_manage') + DELETE = EnumField('cluster_delete', label='cluster_delete') + USE = EnumField('cluster_use', label='cluster_use') + + +@attr.dataclass +class ClusterPermCtx(PermCtx): + project_id: str = '' + cluster_id: Optional[str] = None + + @property + def resource_id(self) -> str: + return self.cluster_id + + +class ClusterRequest(ResourceRequest): + resource_type: str = ResourceType.Cluster + attr = {'_bk_iam_path_': f'/project,{{project_id}}/'} + + def _make_attribute(self, res_id: str) -> Dict: + return {'_bk_iam_path_': self.attr['_bk_iam_path_'].format(project_id=self.attr_kwargs['project_id'])} + + def _validate_attr_kwargs(self): + if not self.attr_kwargs.get('project_id'): + raise AttrValidationError('missing project_id or project_id is invalid') + + +class related_cluster_perm(decorators.RelatedPermission): + + module_name: str = ResourceType.Cluster + + def _convert_perm_ctx(self, instance, args, kwargs) -> PermCtx: + """仅支持第一个参数是 PermCtx 子类实例""" + if len(args) <= 0: + raise TypeError('missing ClusterPermCtx instance argument') + if isinstance(args[0], PermCtx): + return ClusterPermCtx( + username=args[0].username, project_id=args[0].project_id, cluster_id=args[0].cluster_id + ) + else: + raise TypeError('missing ClusterPermCtx instance argument') + + +class cluster_perm(decorators.Permission): + module_name: str = ResourceType.Cluster + + +class ClusterPermission(Permission): + """集群权限""" + + resource_type: str = ResourceType.Cluster + resource_request_cls: Type[ResourceRequest] = ClusterRequest + parent_res_perm = ProjectPermission() + + @related_project_perm(method_name='can_view') + def can_create(self, perm_ctx: ClusterPermCtx, raise_exception: bool = True) -> bool: + return self.can_action(perm_ctx, ClusterAction.CREATE, raise_exception) + + @related_project_perm(method_name='can_view') + def can_view(self, perm_ctx: ClusterPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, ClusterAction.VIEW, raise_exception) + + @related_cluster_perm(method_name='can_view') + def can_manage(self, perm_ctx: ClusterPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, ClusterAction.MANAGE, raise_exception) + + @related_cluster_perm(method_name='can_view') + def can_delete(self, perm_ctx: ClusterPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, ClusterAction.DELETE, raise_exception) + + @related_cluster_perm(method_name='can_view') + def can_use(self, perm_ctx: ClusterPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, ClusterAction.USE, raise_exception) + + def make_res_request(self, res_id: str, perm_ctx: ClusterPermCtx) -> ResourceRequest: + return self.resource_request_cls(res_id, project_id=perm_ctx.project_id) + + def get_parent_chain(self, perm_ctx: ClusterPermCtx) -> List[IAMResource]: + return [IAMResource(ResourceType.Project, perm_ctx.project_id)] + + def get_resource_id(self, perm_ctx: ClusterPermCtx) -> Optional[str]: + return perm_ctx.cluster_id diff --git a/bcs-app/backend/iam/permissions/resources/constants.py b/bcs-app/backend/iam/permissions/resources/constants.py new file mode 100644 index 000000000..85b62c74b --- /dev/null +++ b/bcs-app/backend/iam/permissions/resources/constants.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from backend.packages.blue_krill.data_types.enum import StructuredEnum + + +class ResourceType(str, StructuredEnum): + Project = 'project' + Cluster = 'cluster' + Namespace = 'namespace' + Templateset = "templateset" diff --git a/bcs-app/backend/iam/permissions/resources/namespace.py b/bcs-app/backend/iam/permissions/resources/namespace.py new file mode 100644 index 000000000..e645373fb --- /dev/null +++ b/bcs-app/backend/iam/permissions/resources/namespace.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import Dict, List, Optional, Type + +import attr + +from backend.iam.permissions import decorators +from backend.iam.permissions.exceptions import AttrValidationError +from backend.iam.permissions.perm import PermCtx, Permission +from backend.iam.permissions.request import IAMResource, ResourceRequest +from backend.packages.blue_krill.data_types.enum import EnumField, StructuredEnum + +from .cluster import ClusterPermission, related_cluster_perm +from .constants import ResourceType + + +def calc_iam_ns_id(cluster_id: str, name: Optional[str] = None, max_length: int = 32) -> Optional[str]: + """ + 计算出注册到权限中心的命名空间ID,具备唯一性. + TODO 下一个 PR 中实现,方便 review + """ + return "default" + + +class NamespaceAction(str, StructuredEnum): + CREATE = EnumField('namespace_create', label='namespace_create') + VIEW = EnumField('namespace_view', label='namespace_view') + UPDATE = EnumField('namespace_update', label='namespace_update') + DELETE = EnumField('namespace_delete', label='namespace_delete') + USE = EnumField('namespace_use', label='namespace_use') + + +@attr.dataclass +class NamespacePermCtx(PermCtx): + project_id: str = '' + cluster_id: str = '' + name: Optional[str] = None # 命名空间名 + iam_ns_id: Optional[str] = None # 注册到权限中心的命名空间ID + + def __attrs_post_init__(self): + """权限中心的 resource_id 长度限制为32位""" + self.iam_ns_id = calc_iam_ns_id(self.cluster_id, self.name) + + @property + def resource_id(self) -> str: + return self.iam_ns_id + + +class NamespaceRequest(ResourceRequest): + resource_type: str = ResourceType.Namespace + attr = {'_bk_iam_path_': f'/project,{{project_id}}/cluster,{{cluster_id}}/'} + + def _make_attribute(self, res_id: str) -> Dict: + return { + '_bk_iam_path_': self.attr['_bk_iam_path_'].format( + project_id=self.attr_kwargs['project_id'], cluster_id=self.attr_kwargs['cluster_id'] + ) + } + + def _validate_attr_kwargs(self): + if not self.attr_kwargs.get('project_id'): + raise AttrValidationError('missing project_id or project_id is invalid') + + if not self.attr_kwargs.get('cluster_id'): + raise AttrValidationError('missing cluster_id or cluster_id is invalid') + + +class related_namespace_perm(decorators.RelatedPermission): + + module_name: str = ResourceType.Namespace + + def _convert_perm_ctx(self, instance, args, kwargs) -> PermCtx: + """仅支持第一个参数是 PermCtx 子类实例""" + if len(args) <= 0: + raise TypeError('missing NamespacePermCtx instance argument') + if isinstance(args[0], PermCtx): + return NamespacePermCtx( + username=args[0].username, + project_id=args[0].project_id, + cluster_id=args[0].cluster_id, + name=args[0].name, + ) + else: + raise TypeError('missing NamespacePermCtx instance argument') + + +class namespace_perm(decorators.Permission): + module_name: str = ResourceType.Namespace + + +class NamespacePermission(Permission): + """命名空间权限""" + + resource_type: str = ResourceType.Namespace + resource_request_cls: Type[ResourceRequest] = NamespaceRequest + parent_res_perm = ClusterPermission() + + @related_cluster_perm(method_name='can_use') + def can_create(self, perm_ctx: NamespacePermCtx, raise_exception: bool = True) -> bool: + return self.can_action(perm_ctx, NamespaceAction.CREATE, raise_exception) + + @related_cluster_perm(method_name='can_view') + def can_view(self, perm_ctx: NamespacePermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, NamespaceAction.VIEW, raise_exception) + + @related_cluster_perm(method_name='can_use') + def can_update(self, perm_ctx: NamespacePermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action_with_view(perm_ctx, NamespaceAction.UPDATE, NamespaceAction.VIEW, raise_exception) + + @related_cluster_perm(method_name='can_use') + def can_delete(self, perm_ctx: NamespacePermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action_with_view(perm_ctx, NamespaceAction.DELETE, NamespaceAction.VIEW, raise_exception) + + @related_cluster_perm(method_name='can_use') + def can_use(self, perm_ctx: NamespacePermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action_with_view(perm_ctx, NamespaceAction.USE, NamespaceAction.VIEW, raise_exception) + + def make_res_request(self, res_id: str, perm_ctx: NamespacePermCtx) -> ResourceRequest: + return self.resource_request_cls(res_id, project_id=perm_ctx.project_id, cluster_id=perm_ctx.cluster_id) + + def get_parent_chain(self, perm_ctx: NamespacePermCtx) -> List[IAMResource]: + return [ + IAMResource(ResourceType.Project, perm_ctx.project_id), + IAMResource(ResourceType.Cluster, perm_ctx.cluster_id), + ] + + def get_resource_id(self, perm_ctx: NamespacePermCtx) -> Optional[str]: + return perm_ctx.iam_ns_id diff --git a/bcs-app/backend/iam/permissions/resources/project.py b/bcs-app/backend/iam/permissions/resources/project.py new file mode 100644 index 000000000..66a3d34b1 --- /dev/null +++ b/bcs-app/backend/iam/permissions/resources/project.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import List, Optional, Type + +import attr + +from backend.iam.permissions import decorators +from backend.iam.permissions.perm import PermCtx, Permission, ResourceRequest +from backend.iam.permissions.request import IAMResource +from backend.packages.blue_krill.data_types.enum import EnumField, StructuredEnum + +from .constants import ResourceType + + +class ProjectAction(str, StructuredEnum): + CREATE = EnumField('project_create', label='project_create') + VIEW = EnumField('project_view', label='project_view') + EDIT = EnumField('project_edit', label='project_edit') + + +class ProjectRequest(ResourceRequest): + resource_type: str = ResourceType.Project + + +@attr.dataclass +class ProjectPermCtx(PermCtx): + project_id: Optional[str] = None + + @property + def resource_id(self) -> str: + return self.project_id + + +class related_project_perm(decorators.RelatedPermission): + + module_name: str = ResourceType.Project + + def _convert_perm_ctx(self, instance, args, kwargs) -> PermCtx: + """仅支持第一个参数是 PermCtx 子类实例""" + if len(args) <= 0: + raise TypeError('missing ProjectPermCtx instance argument') + if isinstance(args[0], PermCtx): + return ProjectPermCtx(username=args[0].username, project_id=args[0].project_id) + else: + raise TypeError('missing ProjectPermCtx instance argument') + + +class ProjectPermission(Permission): + """项目权限""" + + resource_type: str = ResourceType.Project + resource_request_cls: Type[ResourceRequest] = ProjectRequest + + def can_create(self, perm_ctx: ProjectPermCtx, raise_exception: bool = True) -> bool: + return self.can_action(perm_ctx, ProjectAction.CREATE, raise_exception) + + def can_view(self, perm_ctx: ProjectPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, ProjectAction.VIEW, raise_exception) + + @related_project_perm(method_name='can_view') + def can_edit(self, perm_ctx: ProjectPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, ProjectAction.EDIT, raise_exception) + + def get_parent_chain(self, perm_ctx: ProjectPermCtx) -> List[IAMResource]: + return [] + + def get_resource_id(self, perm_ctx: ProjectPermCtx) -> Optional[str]: + return perm_ctx.project_id diff --git a/bcs-app/backend/iam/permissions/resources/templateset.py b/bcs-app/backend/iam/permissions/resources/templateset.py new file mode 100644 index 000000000..791440478 --- /dev/null +++ b/bcs-app/backend/iam/permissions/resources/templateset.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import Dict, List, Optional, Type, Union + +import attr + +from backend.iam.permissions import decorators +from backend.iam.permissions.exceptions import AttrValidationError +from backend.iam.permissions.perm import PermCtx, Permission +from backend.iam.permissions.request import IAMResource, ResourceRequest +from backend.packages.blue_krill.data_types.enum import EnumField, StructuredEnum + +from .constants import ResourceType +from .project import ProjectPermission, related_project_perm + + +class TemplatesetAction(str, StructuredEnum): + CREATE = EnumField("templateset_create", label="templateset_create") + VIEW = EnumField("templateset_view", label="templateset_view") + UPDATE = EnumField("templateset_update", label="templateset_update") + DELETE = EnumField("templateset_delete", label="templateset_delete") + INSTANTIATE = EnumField("templateset_instantiate", label="templateset_instantiate") + COPY = EnumField("templateset_copy", label="templateset_copy") + + +@attr.dataclass +class TemplatesetPermCtx(PermCtx): + project_id: str = '' + template_id: Union[str, int, None] = None + + def __attrs_post_init__(self): + if self.template_id: + self.template_id = str(self.template_id) + + @property + def resource_id(self) -> str: + return self.template_id + + +class TemplatesetRequest(ResourceRequest): + resource_type: str = ResourceType.Templateset + attr = {'_bk_iam_path_': f'/project,{{project_id}}/'} + + def _make_attribute(self, res_id: str) -> Dict: + return {'_bk_iam_path_': self.attr['_bk_iam_path_'].format(project_id=self.attr_kwargs['project_id'])} + + def _validate_attr_kwargs(self): + if not self.attr_kwargs.get('project_id'): + raise AttrValidationError('missing project_id or project_id is invalid') + + +class related_templateset_perm(decorators.RelatedPermission): + module_name: str = ResourceType.Templateset + + def _convert_perm_ctx(self, instance, args, kwargs) -> PermCtx: + """仅支持第一个参数是 PermCtx 子类实例""" + if len(args) <= 0: + raise TypeError('missing TemplatesetPermCtx instance a rgument') + if isinstance(args[0], PermCtx): + return TemplatesetPermCtx( + username=args[0].username, project_id=args[0].project_id, template_id=args[0].template_id + ) + else: + raise TypeError('missing TemplatesetPermCtx instance argument') + + +class templateset_perm(decorators.Permission): + module_name: str = ResourceType.Templateset + + +class TemplatesetPermission(Permission): + """模板集权限""" + + resource_type: str = ResourceType.Templateset + resource_request_cls: Type[ResourceRequest] = TemplatesetRequest + parent_res_perm = ProjectPermission() + + @related_project_perm(method_name="can_view") + def can_create(self, perm_ctx: TemplatesetPermCtx, raise_exception: bool = True) -> bool: + return self.can_action(perm_ctx, TemplatesetAction.CREATE, raise_exception) + + @related_project_perm(method_name="can_view") + def can_view(self, perm_ctx: TemplatesetPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, TemplatesetAction.VIEW, raise_exception) + + @related_templateset_perm(method_name='can_view') + def can_copy(self, perm_ctx: TemplatesetPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, TemplatesetAction.COPY, raise_exception) + + @related_templateset_perm(method_name='can_view') + def can_update(self, perm_ctx: TemplatesetPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, TemplatesetAction.UPDATE, raise_exception) + + @related_templateset_perm(method_name='can_view') + def can_delete(self, perm_ctx: TemplatesetPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, TemplatesetAction.DELETE, raise_exception) + + @related_templateset_perm(method_name='can_view') + def can_instantiate(self, perm_ctx: TemplatesetPermCtx, raise_exception: bool = True) -> bool: + perm_ctx.validate_resource_id() + return self.can_action(perm_ctx, TemplatesetAction.INSTANTIATE, raise_exception) + + def make_res_request(self, res_id: str, perm_ctx: TemplatesetPermCtx) -> ResourceRequest: + return self.resource_request_cls(res_id, project_id=perm_ctx.project_id) + + def get_parent_chain(self, perm_ctx: TemplatesetPermCtx) -> List[IAMResource]: + return [IAMResource(ResourceType.Project, perm_ctx.project_id)] + + def get_resource_id(self, perm_ctx: TemplatesetPermCtx) -> Optional[str]: + return perm_ctx.template_id diff --git a/bcs-app/backend/settings/ce/base.py b/bcs-app/backend/settings/ce/base.py index 72de8f3ea..10a92f7bc 100644 --- a/bcs-app/backend/settings/ce/base.py +++ b/bcs-app/backend/settings/ce/base.py @@ -36,7 +36,7 @@ "backend.uniapps.apis", "backend.bcs_web.apis.apps.APIConfig", "iam.contrib.iam_migration", - "backend.bcs_web.iam.bcs_iam_migration.apps.BcsIamMigrationConfig", + "backend.iam.bcs_iam_migration.apps.BcsIamMigrationConfig", ] # 统一登录页面 diff --git a/bcs-app/backend/settings/ce/dev.py b/bcs-app/backend/settings/ce/dev.py index e90c49324..965c86d9a 100644 --- a/bcs-app/backend/settings/ce/dev.py +++ b/bcs-app/backend/settings/ce/dev.py @@ -37,7 +37,7 @@ ] # 本地开发先去除权限中心v3的数据初始逻辑 -INSTALLED_APPS.remove("backend.bcs_web.iam.bcs_iam_migration.apps.BcsIamMigrationConfig") +INSTALLED_APPS.remove("backend.iam.bcs_iam_migration.apps.BcsIamMigrationConfig") LOG_LEVEL = "DEBUG" LOGGING = get_logging_config(LOG_LEVEL) diff --git a/bcs-app/backend/tests/bcs_mocks/data/paas_cc_json.py b/bcs-app/backend/tests/bcs_mocks/data/paas_cc_json.py index 0090fa371..36a22ecd3 100644 --- a/bcs-app/backend/tests/bcs_mocks/data/paas_cc_json.py +++ b/bcs-app/backend/tests/bcs_mocks/data/paas_cc_json.py @@ -51,6 +51,78 @@ "request_id": uuid.uuid4().hex, } +resp_filter_projects_ok = { + "data": { + "results": [ + { + "approval_status": 2, + "approval_time": "2020-01-01T00:00:00+08:00", + "approver": "", + "bg_id": -1, + "bg_name": "", + "cc_app_id": 100, + "center_id": 100, + "center_name": "", + "created_at": "2020-01-01 00:00:00", + "creator": "unknown", + "data_id": 0, + "deploy_type": "null", + "dept_id": -1, + "dept_name": "", + "description": "", + "english_name": "unittest-cluster", + "extra": {}, + "is_offlined": False, + "is_secrecy": False, + "kind": 1, + "logo_addr": "", + "project_id": uuid.uuid4().hex, + "project_name": "unittest-cluster", + "project_type": 1, + "remark": "", + "updated_at": "2020-01-01 00:00:00", + "use_bk": False, + "cc_app_name": "demo-app", + "can_edit": False, + }, + { + "approval_status": 2, + "approval_time": "2020-01-01T00:00:00+08:00", + "approver": "", + "bg_id": -1, + "bg_name": "", + "cc_app_id": 100, + "center_id": 100, + "center_name": "", + "created_at": "2020-01-01 00:00:00", + "creator": "unknown", + "data_id": 0, + "deploy_type": "null", + "dept_id": -1, + "dept_name": "", + "description": "", + "english_name": "unittest-cluster-a", + "extra": {}, + "is_offlined": False, + "is_secrecy": False, + "kind": 1, + "logo_addr": "", + "project_id": uuid.uuid4().hex, + "project_name": "unittest-cluster-a", + "project_type": 1, + "remark": "", + "updated_at": "2020-01-01 00:00:00", + "use_bk": False, + "cc_app_name": "demo-app", + "can_edit": False, + }, + ] + }, + "code": 0, + "message": "OK", + "request_id": uuid.uuid4().hex, +} + resp_get_clusters_ok = { "code": 0, "data": { diff --git a/bcs-app/backend/tests/bcs_mocks/misc.py b/bcs-app/backend/tests/bcs_mocks/misc.py index b151c5ee0..caf5349d3 100644 --- a/bcs-app/backend/tests/bcs_mocks/misc.py +++ b/bcs-app/backend/tests/bcs_mocks/misc.py @@ -13,7 +13,7 @@ specific language governing permissions and limitations under the License. """ import copy -from typing import Dict +from typing import Dict, Optional from .data import paas_cc_json @@ -26,6 +26,17 @@ def get_project(self, access_token: str, project_id: str) -> Dict: resp_get_project_ok['data']['project_id'] = project_id return self._resp(resp_get_project_ok) + def get_projects(self, access_token: str, query_params: Optional[Dict]) -> Dict: + resp = self._resp(paas_cc_json.resp_filter_projects_ok) + + if not query_params: + return resp + + project_id_list = query_params['project_ids'].split(',') + resp['data']['results'][0]['project_id'] = project_id_list[0] + + return resp + def get_all_clusters(self, access_token, project_id, limit=None, offset=None, desire_all_data=0): resp = self._resp(paas_cc_json.resp_get_clusters_ok) for info in resp['data']['results']: diff --git a/bcs-app/backend/tests/components/test_paas_cc.py b/bcs-app/backend/tests/components/test_paas_cc.py index a73055a9b..2d6ea694d 100644 --- a/bcs-app/backend/tests/components/test_paas_cc.py +++ b/bcs-app/backend/tests/components/test_paas_cc.py @@ -27,6 +27,14 @@ def test_get_cluster_simple(self, project_id, cluster_id, requests_mock): assert resp == {'foo': 'bar'} assert requests_mock.called + def test_get_cluster_by_id(self, cluster_id, requests_mock): + requests_mock.get(ANY, json={'code': 0, 'data': {'cluster_id': cluster_id}}) + + client = PaaSCCClient(ComponentAuth('token')) + resp = client.get_cluster_by_id(cluster_id) + assert resp == {'cluster_id': cluster_id} + assert requests_mock.called + def test_update_cluster(self, project_id, cluster_id, requests_mock): requests_mock.put( ANY, json={"code": 0, "data": {"cluster_id": cluster_id, "project_id": project_id, "status": "normal"}} diff --git a/bcs-app/backend/tests/components/test_permissions.py b/bcs-app/backend/tests/components/test_permissions.py index 606786205..53ffb8a4a 100644 --- a/bcs-app/backend/tests/components/test_permissions.py +++ b/bcs-app/backend/tests/components/test_permissions.py @@ -15,7 +15,7 @@ import pytest from iam import OP -from backend.bcs_web.iam.permissions import ProjectPermission +from backend.iam.legacy_perms import ProjectPermission test_dict_filter_data = [ ({'op': OP.IN, 'value': [2, 1], 'field': 'project.id'}, {'project_id_list': [1, 2], 'op': OP.IN}), diff --git a/bcs-app/backend/bcs_web/iam/__init__.py b/bcs-app/backend/tests/iam/__init__.py similarity index 100% rename from bcs-app/backend/bcs_web/iam/__init__.py rename to bcs-app/backend/tests/iam/__init__.py diff --git a/bcs-app/backend/tests/iam/conftest.py b/bcs-app/backend/tests/iam/conftest.py new file mode 100644 index 000000000..f411532e6 --- /dev/null +++ b/bcs-app/backend/tests/iam/conftest.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import List +from unittest import mock + +import pytest + +from backend.iam.permissions.apply_url import ApplyURLGenerator +from backend.iam.permissions.request import ActionResourcesRequest +from backend.iam.permissions.resources.cluster import ClusterPermission +from backend.iam.permissions.resources.namespace import NamespacePermission +from backend.iam.permissions.resources.project import ProjectPermission + +from .fake_iam import FakeClusterPermission, FakeNamespacePermission, FakeProjectPermission + + +def generate_apply_url(username: str, action_request_list: List[ActionResourcesRequest]) -> List[str]: + expect = [] + for req in action_request_list: + resources = '' + if req.resources: + resources = ''.join(req.resources) + + parent_chain = '' + if req.parent_chain: + parent_chain = ''.join([f'{item.resource_type}/{item.resource_id}' for item in req.parent_chain]) + expect.append(f'{req.resource_type}:{req.action_id}:{resources}:{parent_chain}') + + return expect + + +@pytest.fixture(autouse=True) +def patch_generate_apply_url(): + with mock.patch.object(ApplyURLGenerator, 'generate_apply_url', new=generate_apply_url): + yield + + +@pytest.fixture +def project_permission_obj(): + patcher = mock.patch.object(ProjectPermission, '__bases__', (FakeProjectPermission,)) + with patcher: + patcher.is_local = True # 标注为本地属性,__exit__ 的时候恢复成 patcher.temp_original + yield ProjectPermission() + + +@pytest.fixture +def namespace_permission_obj(): + cluster_patcher = mock.patch.object(ClusterPermission, '__bases__', (FakeClusterPermission,)) + project_patcher = mock.patch.object(ProjectPermission, '__bases__', (FakeProjectPermission,)) + namespace_patcher = mock.patch.object(NamespacePermission, '__bases__', (FakeNamespacePermission,)) + with cluster_patcher, project_patcher, namespace_patcher: + cluster_patcher.is_local = True # 标注为本地属性,__exit__ 的时候恢复成 patcher.temp_original + project_patcher.is_local = True + namespace_patcher.is_local = True + yield NamespacePermission() diff --git a/bcs-app/backend/tests/iam/fake_iam.py b/bcs-app/backend/tests/iam/fake_iam.py new file mode 100644 index 000000000..c4c292f38 --- /dev/null +++ b/bcs-app/backend/tests/iam/fake_iam.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from iam import Request + +from backend.iam.permissions.perm import Permission +from backend.iam.permissions.resources.project import ProjectAction + +from .permissions import roles + + +class FakeProjectIAM: + def is_allowed(self, request: Request) -> bool: + if request.subject.id in [roles.ADMIN_USER, roles.PROJECT_CLUSTER_USER, roles.PROJECT_NO_CLUSTER_USER]: + return True + + if request.subject.id == roles.PROJECT_NO_VIEW_USER: + if request.action.id == ProjectAction.VIEW: + return False + return True + + return False + + def is_allowed_with_cache(self, request: Request) -> bool: + return self.is_allowed(request) + + +class FakeProjectPermission(Permission): + iam = FakeProjectIAM() + + +class FakeClusterIAM: + def is_allowed(self, request: Request) -> bool: + if request.subject.id in [ + roles.ADMIN_USER, + roles.CLUSTER_USER, + roles.PROJECT_CLUSTER_USER, + roles.CLUSTER_NO_PROJECT_USER, + ]: + return True + return False + + def is_allowed_with_cache(self, request: Request) -> bool: + return self.is_allowed(request) + + +class FakeClusterPermission(Permission): + iam = FakeClusterIAM() + + +class FakeNamespaceIAM: + def is_allowed(self, request: Request) -> bool: + if request.subject.id in [roles.ADMIN_USER, roles.NAMESPACE_NO_CLUSTER_PROJECT_USER]: + return True + return False + + def is_allowed_with_cache(self, request: Request) -> bool: + return self.is_allowed(request) + + +class FakeNamespacePermission(Permission): + iam = FakeNamespaceIAM() + + +class FakeTemplatesetIAM: + def __init__(self, *args, **kwargs): + """""" + + def is_allowed(self, request: Request) -> bool: + if request.subject.id in [ + roles.ADMIN_USER, + roles.TEMPLATESET_USER, + roles.PROJECT_TEMPLATESET_USER, + roles.TEMPLATESET_NO_PROJECT_USER, + ]: + return True + return False + + def is_allowed_with_cache(self, request: Request) -> bool: + return self.is_allowed(request) + + +class FakeTemplatesetPermission(Permission): + iam = FakeTemplatesetIAM() diff --git a/bcs-app/backend/tests/iam/open_apis/__init__.py b/bcs-app/backend/tests/iam/open_apis/__init__.py new file mode 100644 index 000000000..08f8f6ab8 --- /dev/null +++ b/bcs-app/backend/tests/iam/open_apis/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" diff --git a/bcs-app/backend/tests/iam/open_apis/conftest.py b/bcs-app/backend/tests/iam/open_apis/conftest.py new file mode 100644 index 000000000..bdef52054 --- /dev/null +++ b/bcs-app/backend/tests/iam/open_apis/conftest.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import mock +import pytest + + +@pytest.fixture(autouse=True) +def patch4resource_api(): + with mock.patch( + 'backend.iam.open_apis.authentication.IamBasicAuthentication.authenticate', new=lambda *args, **kwargs: None + ), mock.patch( + 'backend.components.ssm.get_client_access_token', new=lambda *args, **kwargs: {"access_token": "test"} + ): + yield diff --git a/bcs-app/backend/tests/iam/open_apis/test_namespace.py b/bcs-app/backend/tests/iam/open_apis/test_namespace.py new file mode 100644 index 000000000..5f9117580 --- /dev/null +++ b/bcs-app/backend/tests/iam/open_apis/test_namespace.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import mock +import pytest +from rest_framework.test import APIRequestFactory + +from backend.iam.open_apis.views import ResourceAPIView +from backend.tests.testing_utils.mocks.paas_cc import StubPaaSCCClient + +factory = APIRequestFactory() + + +@pytest.fixture(autouse=True) +def patch_paas_cc(): + with mock.patch('backend.iam.open_apis.provider.namespace.PaaSCCClient', new=StubPaaSCCClient): + yield + + +class TestNamespaceAPI: + def test_list_instance(self, project_id): + request = factory.post( + '/apis/iam/v1/namespaces/', + { + 'method': 'list_instance', + 'type': 'namespace', + 'page': {'offset': 0, 'limit': 1}, + 'filter': {'parent': {'id': project_id}}, + }, + ) + p_view = ResourceAPIView.as_view() + response = p_view(request) + data = response.data + assert data['count'] == 1 + assert data['results'][0]['display_name'] == 'default' + + def test_fetch_instance_info(self, cluster_id): + fetch_id = f'{cluster_id}:default' + request = factory.post( + '/apis/iam/v1/namespaces/', + { + 'method': 'fetch_instance_info', + 'type': 'namespace', + 'filter': {'ids': [fetch_id], 'parent': {'id': cluster_id}}, + }, + ) + p_view = ResourceAPIView.as_view() + response = p_view(request) + data = response.data + assert len(data) == 1 + assert data[0]['id'] == fetch_id + assert data[0]['display_name'] == 'default' diff --git a/bcs-app/backend/tests/iam/open_apis/test_project.py b/bcs-app/backend/tests/iam/open_apis/test_project.py new file mode 100644 index 000000000..9594d0b04 --- /dev/null +++ b/bcs-app/backend/tests/iam/open_apis/test_project.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import mock +import pytest +from rest_framework.test import APIRequestFactory + +from backend.iam.open_apis.views import ResourceAPIView +from backend.tests.bcs_mocks.misc import FakePaaSCCMod + +factory = APIRequestFactory() + + +@pytest.fixture(autouse=True) +def patch_paas_cc(): + with mock.patch('backend.container_service.projects.base.utils.paas_cc', new=FakePaaSCCMod()): + yield + + +class TestProjectAPI: + def test_list_instance(self): + request = factory.post( + '/apis/iam/v1/projects/', {'method': 'list_instance', 'type': 'project', 'page': {'offset': 0, 'limit': 1}} + ) + p_view = ResourceAPIView.as_view() + response = p_view(request) + data = response.data + assert data['count'] == 2 + assert data['results'][0]['display_name'] == 'unittest-cluster' + + def test_fetch_instance_info(self, project_id): + request = factory.post( + '/apis/iam/v1/projects/', + { + 'method': 'fetch_instance_info', + 'type': 'project', + 'filter': {'ids': [project_id]}, + }, + ) + p_view = ResourceAPIView.as_view() + response = p_view(request) + data = response.data + assert data[0]['display_name'] == 'unittest-cluster' + assert data[0]['id'] == project_id diff --git a/bcs-app/backend/tests/iam/permissions/__init__.py b/bcs-app/backend/tests/iam/permissions/__init__.py new file mode 100644 index 000000000..08f8f6ab8 --- /dev/null +++ b/bcs-app/backend/tests/iam/permissions/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" diff --git a/bcs-app/backend/tests/iam/permissions/conftest.py b/bcs-app/backend/tests/iam/permissions/conftest.py new file mode 100644 index 000000000..89034aad0 --- /dev/null +++ b/bcs-app/backend/tests/iam/permissions/conftest.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import pytest + +from backend.tests.testing_utils.base import generate_random_string + + +@pytest.fixture +def namespace_name(): + return generate_random_string(16) + + +@pytest.fixture +def template_id(): + """生成一个随机模板集 ID""" + return generate_random_string(32) + + +@pytest.fixture +def template_name(): + """生成一个随机模板集 name""" + return generate_random_string(16) diff --git a/bcs-app/backend/tests/iam/permissions/roles.py b/bcs-app/backend/tests/iam/permissions/roles.py new file mode 100644 index 000000000..c87f072ee --- /dev/null +++ b/bcs-app/backend/tests/iam/permissions/roles.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" + +ADMIN_USER = 'admin' + +ANONYMOUS_USER = 'anonymous_user' + +PROJECT_NO_VIEW_USER = 'project_no_view_user' + +NO_PROJECT_USER = 'no_project_user' + +CLUSTER_USER = 'cluster_user' + +PROJECT_CLUSTER_USER = 'project_cluster_user' + +PROJECT_NO_CLUSTER_USER = 'project_no_cluster_user' + +CLUSTER_NO_PROJECT_USER = 'cluster_no_project_user' + +NAMESPACE_NO_CLUSTER_PROJECT_USER = 'namespace_no_cluster_project_user' + +TEMPLATESET_USER = "templateset_user" + +PROJECT_TEMPLATESET_USER = 'project_templateset_user' + +PROJECT_NO_TEMPLATESET_USER = 'project_no_templateset_user' + +TEMPLATESET_NO_PROJECT_USER = 'templateset_no_project_user' diff --git a/bcs-app/backend/tests/iam/permissions/test_cluster.py b/bcs-app/backend/tests/iam/permissions/test_cluster.py new file mode 100644 index 000000000..2c07d7981 --- /dev/null +++ b/bcs-app/backend/tests/iam/permissions/test_cluster.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from unittest import mock + +import pytest + +from backend.iam.permissions.exceptions import PermissionDeniedError +from backend.iam.permissions.request import ActionResourcesRequest, IAMResource +from backend.iam.permissions.resources.cluster import ClusterAction, ClusterPermCtx, ClusterPermission, cluster_perm +from backend.iam.permissions.resources.constants import ResourceType +from backend.iam.permissions.resources.project import ProjectAction, ProjectPermission +from backend.tests.iam.conftest import generate_apply_url + +from ..fake_iam import FakeClusterPermission, FakeProjectPermission +from . import roles + + +@pytest.fixture +def cluster_permission_obj(): + cluster_patcher = mock.patch.object(ClusterPermission, '__bases__', (FakeClusterPermission,)) + project_patcher = mock.patch.object(ProjectPermission, '__bases__', (FakeProjectPermission,)) + with cluster_patcher, project_patcher: + cluster_patcher.is_local = True # 标注为本地属性,__exit__ 的时候恢复成 patcher.temp_original + project_patcher.is_local = True + yield ClusterPermission() + + +class TestClusterPermission: + """ + 集群资源权限 + note: 仅测试 cluster_create 和 cluster_view 两种代表性的权限,其他操作权限逻辑重复 + """ + + def test_can_create(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:有集群创建权限(同时有项目查看权限)""" + perm_ctx = ClusterPermCtx(username=roles.ADMIN_USER, project_id=project_id) + assert cluster_permission_obj.can_create(perm_ctx) + + def test_can_not_create(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:无集群创建权限(同时无项目查看权限)""" + perm_ctx = ClusterPermCtx(username=roles.ANONYMOUS_USER, project_id=project_id) + with pytest.raises(PermissionDeniedError) as exec: + cluster_permission_obj.can_create(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + roles.ANONYMOUS_USER, + [ + ActionResourcesRequest( + ClusterAction.CREATE, + resource_type=ProjectPermission.resource_type, + resources=[project_id], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) + + def test_can_view(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:有集群查看权限(同时有项目查看权限)""" + perm_ctx = ClusterPermCtx(username=roles.ADMIN_USER, project_id=project_id, cluster_id=cluster_id) + assert cluster_permission_obj.can_view(perm_ctx) + + def test_can_not_view_but_project(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:无集群查看权限(同时有项目查看权限)""" + self._test_can_not_view( + roles.PROJECT_NO_CLUSTER_USER, + cluster_permission_obj, + project_id, + cluster_id, + expected_action_list=[ + ActionResourcesRequest( + ClusterAction.VIEW, + resource_type=cluster_permission_obj.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) + + def test_can_view_but_no_project(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:有集群查看权限(同时无项目查看权限)""" + self._test_can_not_view( + roles.CLUSTER_NO_PROJECT_USER, + cluster_permission_obj, + project_id, + cluster_id, + expected_action_list=[ + ActionResourcesRequest( + ProjectAction.VIEW, + resource_type=ProjectPermission.resource_type, + resources=[project_id], + ) + ], + ) + + def test_can_not_view(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:无集群查看权限(同时无项目查看权限)""" + self._test_can_not_view( + roles.ANONYMOUS_USER, + cluster_permission_obj, + project_id, + cluster_id, + expected_action_list=[ + ActionResourcesRequest( + ClusterAction.VIEW, + resource_type=cluster_permission_obj.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, + resource_type=ProjectPermission.resource_type, + resources=[project_id], + ), + ], + ) + + def _test_can_not_view(self, username, cluster_permission_obj, project_id, cluster_id, expected_action_list): + perm_ctx = ClusterPermCtx(username=username, project_id=project_id, cluster_id=cluster_id) + with pytest.raises(PermissionDeniedError) as exec: + cluster_permission_obj.can_view(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url(username, expected_action_list) + + def test_can_not_manage(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:无集群管理权限(同时无项目查看权限)""" + username = roles.ANONYMOUS_USER + perm_ctx = ClusterPermCtx(username=username, project_id=project_id, cluster_id=cluster_id) + with pytest.raises(PermissionDeniedError) as exec: + cluster_permission_obj.can_manage(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + ClusterAction.MANAGE, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ClusterAction.VIEW, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) + + def test_can_manage_but_no_project(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:有集群管理权限(同时无项目权限)""" + username = roles.CLUSTER_NO_PROJECT_USER + perm_ctx = ClusterPermCtx(username=username, project_id=project_id, cluster_id=cluster_id) + with pytest.raises(PermissionDeniedError) as exec: + cluster_permission_obj.can_manage(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ) + ], + ) + + +@cluster_perm(method_name='can_manage') +def manage_cluster(perm_ctx: ClusterPermCtx): + """""" + + +class TestClusterPermDecorator: + def test_can_manage(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:有集群管理权限(同时有项目查看权限)""" + perm_ctx = ClusterPermCtx(username=roles.ADMIN_USER, project_id=project_id, cluster_id=cluster_id) + manage_cluster(perm_ctx) + + def test_can_not_manage(self, cluster_permission_obj, project_id, cluster_id): + """测试场景:无集群管理权限(同时无项目查看权限)""" + username = roles.ANONYMOUS_USER + perm_ctx = ClusterPermCtx(username=username, project_id=project_id, cluster_id=cluster_id) + with pytest.raises(PermissionDeniedError) as exec: + manage_cluster(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + ClusterAction.MANAGE, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ClusterAction.VIEW, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) diff --git a/bcs-app/backend/tests/iam/permissions/test_namespace.py b/bcs-app/backend/tests/iam/permissions/test_namespace.py new file mode 100644 index 000000000..d7a6f2085 --- /dev/null +++ b/bcs-app/backend/tests/iam/permissions/test_namespace.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import pytest + +from backend.iam.permissions.exceptions import PermissionDeniedError +from backend.iam.permissions.request import ActionResourcesRequest, IAMResource +from backend.iam.permissions.resources.cluster import ClusterAction, ClusterPermission +from backend.iam.permissions.resources.constants import ResourceType +from backend.iam.permissions.resources.namespace import ( + NamespaceAction, + NamespacePermCtx, + NamespacePermission, + namespace_perm, +) +from backend.iam.permissions.resources.project import ProjectAction, ProjectPermission +from backend.tests.iam.conftest import generate_apply_url + +from . import roles + + +class TestNamespacePermission: + """ + 命名空间资源权限 + note: 仅测试 namespace_use 这一代表性的权限,其他操作权限逻辑重复 + """ + + def test_can_create_but_no_cluster_project(self, namespace_permission_obj, project_id, cluster_id): + """测试场景:有命名空间创建权限(同时无集群使用/查看权限、无项目查看权限)""" + username = roles.NAMESPACE_NO_CLUSTER_PROJECT_USER + perm_ctx = NamespacePermCtx(username=username, project_id=project_id, cluster_id=cluster_id) + with pytest.raises(PermissionDeniedError) as exec: + namespace_permission_obj.can_create(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + ClusterAction.USE, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ClusterAction.VIEW, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) + + def test_can_not_use(self, namespace_permission_obj, project_id, cluster_id, namespace_name): + """测试场景:无命名空间使用/查看权限(同时无集群和项目权限)""" + username = roles.ANONYMOUS_USER + perm_ctx = NamespacePermCtx( + username=username, project_id=project_id, cluster_id=cluster_id, name=namespace_name + ) + with pytest.raises(PermissionDeniedError) as exec: + namespace_permission_obj.can_use(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + NamespaceAction.USE, + resource_type=NamespacePermission.resource_type, + resources=[perm_ctx.iam_ns_id], + parent_chain=[ + IAMResource(ResourceType.Project, project_id), + IAMResource(ResourceType.Cluster, cluster_id), + ], + ), + ActionResourcesRequest( + NamespaceAction.VIEW, + resource_type=NamespacePermission.resource_type, + resources=[perm_ctx.iam_ns_id], + parent_chain=[ + IAMResource(ResourceType.Project, project_id), + IAMResource(ResourceType.Cluster, cluster_id), + ], + ), + ActionResourcesRequest( + ClusterAction.USE, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ClusterAction.VIEW, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) + + def test_can_use_but_no_cluster_project(self, namespace_permission_obj, project_id, cluster_id, namespace_name): + """测试场景: 有命名空间使用权限(同时无集群和项目权限)""" + username = roles.NAMESPACE_NO_CLUSTER_PROJECT_USER + perm_ctx = NamespacePermCtx( + username=username, project_id=project_id, cluster_id=cluster_id, name=namespace_name + ) + + # 不抛出异常 + assert not namespace_permission_obj.can_use(perm_ctx, raise_exception=False) + + # 抛出异常 + with pytest.raises(PermissionDeniedError) as exec: + namespace_permission_obj.can_use(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + ClusterAction.USE, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ClusterAction.VIEW, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) + + +@namespace_perm(method_name='can_use') +def helm_install(perm_ctx: NamespacePermCtx): + """helm install 到某个命名空间""" + + +class TestNamespacePermDecorator: + def test_can_use(self, namespace_permission_obj, project_id, cluster_id, namespace_name): + """测试场景:有命名空间使用权限(同时有集群和项目权限)""" + perm_ctx = NamespacePermCtx( + username=roles.ADMIN_USER, project_id=project_id, cluster_id=cluster_id, name=namespace_name + ) + helm_install(perm_ctx) + + def test_can_not_use(self, namespace_permission_obj, project_id, cluster_id, namespace_name): + """测试场景:无命名空间使用权限(同时无集群和项目权限)""" + username = roles.ANONYMOUS_USER + perm_ctx = NamespacePermCtx( + username=username, project_id=project_id, cluster_id=cluster_id, name=namespace_name + ) + with pytest.raises(PermissionDeniedError) as exec: + helm_install(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + NamespaceAction.USE, + resource_type=NamespacePermission.resource_type, + resources=[perm_ctx.iam_ns_id], + parent_chain=[ + IAMResource(ResourceType.Project, project_id), + IAMResource(ResourceType.Cluster, cluster_id), + ], + ), + ActionResourcesRequest( + NamespaceAction.VIEW, + resource_type=NamespacePermission.resource_type, + resources=[perm_ctx.iam_ns_id], + parent_chain=[ + IAMResource(ResourceType.Project, project_id), + IAMResource(ResourceType.Cluster, cluster_id), + ], + ), + ActionResourcesRequest( + ClusterAction.USE, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ClusterAction.VIEW, + resource_type=ClusterPermission.resource_type, + resources=[cluster_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) diff --git a/bcs-app/backend/tests/iam/permissions/test_project.py b/bcs-app/backend/tests/iam/permissions/test_project.py new file mode 100644 index 000000000..c834be0d2 --- /dev/null +++ b/bcs-app/backend/tests/iam/permissions/test_project.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import pytest + +from backend.iam.permissions.exceptions import PermissionDeniedError +from backend.iam.permissions.perm import ActionResourcesRequest +from backend.iam.permissions.resources.project import ProjectAction, ProjectPermCtx +from backend.tests.iam.conftest import generate_apply_url + +from . import roles + + +class TestProjectPermission: + """ + 测试项目资源权限 + note: 仅测试 project_create 和 project_view 两种代表性的权限,其他操作权限逻辑重复 + """ + + def test_can_create(self, project_permission_obj): + """测试场景:有项目创建权限""" + perm_ctx = ProjectPermCtx(username=roles.ADMIN_USER) + assert project_permission_obj.can_create(perm_ctx) + + def test_can_not_create(self, project_permission_obj): + """测试场景:无项目创建权限""" + + # 无权限不抛出异常 + username = roles.NO_PROJECT_USER + perm_ctx = ProjectPermCtx(username=username) + assert not project_permission_obj.can_create(perm_ctx, raise_exception=False) + + # 无权限抛出异常 + with pytest.raises(PermissionDeniedError) as exec: + project_permission_obj.can_create(perm_ctx) + assert exec.value.code == PermissionDeniedError.code + assert exec.value.data['apply_url'] == generate_apply_url( + username, + action_request_list=[ + ActionResourcesRequest(ProjectAction.CREATE, resource_type=project_permission_obj.resource_type) + ], + ) + + def test_can_view(self, project_permission_obj, project_id): + """测试场景:有项目查看权限""" + perm_ctx = ProjectPermCtx(username=roles.ADMIN_USER, project_id=project_id) + assert project_permission_obj.can_view(perm_ctx) + + def test_can_not_view(self, project_permission_obj, project_id): + """测试场景:无项目查看权限""" + + # 无权限不抛出异常 + username = roles.NO_PROJECT_USER + perm_ctx = ProjectPermCtx(username=username, project_id=project_id) + assert not project_permission_obj.can_view(perm_ctx, raise_exception=False) + + # 无权限抛出异常 + with pytest.raises(PermissionDeniedError) as exec: + project_permission_obj.can_view(perm_ctx) + assert exec.value.code == PermissionDeniedError.code + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + ProjectAction.VIEW, + resource_type=project_permission_obj.resource_type, + resources=[project_id], + ) + ], + ) + + def test_can_edit_not_view(self, project_permission_obj, project_id): + """测试场景:有项目编辑权限(同时无项目查看权限)""" + username = roles.PROJECT_NO_VIEW_USER + perm_ctx = ProjectPermCtx(username=username, project_id=project_id) + with pytest.raises(PermissionDeniedError) as exec: + project_permission_obj.can_edit(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + ProjectAction.VIEW, + resource_type=project_permission_obj.resource_type, + resources=[project_id], + ) + ], + ) diff --git a/bcs-app/backend/tests/iam/permissions/test_templateset.py b/bcs-app/backend/tests/iam/permissions/test_templateset.py new file mode 100644 index 000000000..e3e86aeb0 --- /dev/null +++ b/bcs-app/backend/tests/iam/permissions/test_templateset.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +""" +Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community +Edition) available. +Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://opensource.org/licenses/MIT + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from unittest import mock + +import pytest + +from backend.iam.permissions.exceptions import PermissionDeniedError +from backend.iam.permissions.request import ActionResourcesRequest, IAMResource +from backend.iam.permissions.resources.constants import ResourceType +from backend.iam.permissions.resources.project import ProjectAction, ProjectPermission +from backend.iam.permissions.resources.templateset import ( + TemplatesetAction, + TemplatesetPermCtx, + TemplatesetPermission, + templateset_perm, +) +from backend.tests.iam.conftest import generate_apply_url + +from ..fake_iam import FakeProjectPermission, FakeTemplatesetPermission +from . import roles + + +@pytest.fixture +def templateset_permission_obj(): + templateset_patcher = mock.patch.object(TemplatesetPermission, '__bases__', (FakeTemplatesetPermission,)) + project_patcher = mock.patch.object(ProjectPermission, '__bases__', (FakeProjectPermission,)) + with templateset_patcher, project_patcher: + templateset_patcher.is_local = True # 标注为本地属性,__exit__ 的时候恢复成 patcher.temp_original + project_patcher.is_local = True + yield TemplatesetPermission() + + +class TestTemplatesetPermission: + """ + 模板集资源权限 + note: 仅测试 templateset_instantiate,其他操作权限逻辑重复(和TestClusterPermission类似) + """ + + def test_can_instantiate(self, templateset_permission_obj, project_id, template_id): + """测试场景:有模板集实例化权限""" + username = roles.ADMIN_USER + perm_ctx = TemplatesetPermCtx(username=username, project_id=project_id, template_id=template_id) + assert templateset_permission_obj.can_instantiate(perm_ctx) + + def test_can_instantiate_but_no_project(self, templateset_permission_obj, project_id, template_id): + """测试场景:有模板集实例化权限(同时无项目查看权限)""" + username = roles.TEMPLATESET_NO_PROJECT_USER + perm_ctx = TemplatesetPermCtx(username=username, project_id=project_id, template_id=template_id) + with pytest.raises(PermissionDeniedError) as exec: + templateset_permission_obj.can_instantiate(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ) + ], + ) + + def test_can_not_instantiate(self, templateset_permission_obj, project_id, template_id): + """测试场景:无模板集实例化权限(同时无项目查看权限)""" + username = roles.ANONYMOUS_USER + perm_ctx = TemplatesetPermCtx(username=username, project_id=project_id, template_id=template_id) + with pytest.raises(PermissionDeniedError) as exec: + templateset_permission_obj.can_instantiate(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + TemplatesetAction.INSTANTIATE, + resource_type=TemplatesetPermission.resource_type, + resources=[template_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + TemplatesetAction.VIEW, + resource_type=TemplatesetPermission.resource_type, + resources=[template_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) + + +@templateset_perm(method_name='can_instantiate') +def instantiate_templateset(perm_ctx: TemplatesetPermCtx): + """""" + + +class TestTemplatesetPermDecorator: + def test_can_instantiate(self, templateset_permission_obj, project_id, template_id): + """测试场景:有模板集实例化权限""" + perm_ctx = TemplatesetPermCtx(username=roles.ADMIN_USER, project_id=project_id, template_id=template_id) + instantiate_templateset(perm_ctx) + + def test_can_not_instantiate(self, templateset_permission_obj, project_id, template_id): + """测试场景:无模板集实例化权限(同时无项目查看权限)""" + username = roles.ANONYMOUS_USER + perm_ctx = TemplatesetPermCtx(username=username, project_id=project_id, template_id=template_id) + with pytest.raises(PermissionDeniedError) as exec: + instantiate_templateset(perm_ctx) + assert exec.value.data['apply_url'] == generate_apply_url( + username, + [ + ActionResourcesRequest( + TemplatesetAction.INSTANTIATE, + resource_type=TemplatesetPermission.resource_type, + resources=[template_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + TemplatesetAction.VIEW, + resource_type=TemplatesetPermission.resource_type, + resources=[template_id], + parent_chain=[IAMResource(ResourceType.Project, project_id)], + ), + ActionResourcesRequest( + ProjectAction.VIEW, resource_type=ProjectPermission.resource_type, resources=[project_id] + ), + ], + ) diff --git a/bcs-app/backend/tests/testing_utils/mocks/paas_cc.py b/bcs-app/backend/tests/testing_utils/mocks/paas_cc.py index 3b908af02..1c7682df9 100644 --- a/bcs-app/backend/tests/testing_utils/mocks/paas_cc.py +++ b/bcs-app/backend/tests/testing_utils/mocks/paas_cc.py @@ -28,6 +28,10 @@ def __init__(self, *args, **kwargs): def get_cluster(self, project_id: str, cluster_id: str) -> Dict: return self.wrap_resp(self.make_cluster_data(project_id, cluster_id)) + @mockable_function + def get_cluster_by_id(self, cluster_id: str) -> Dict: + return self.make_cluster_data_by_id(cluster_id) + @mockable_function def get_project(self, project_id: str) -> Dict: return self.make_project_data(project_id) @@ -80,6 +84,40 @@ def make_cluster_data(project_id: str, cluster_id: str): 'updated_at': _stub_time, } + @staticmethod + def make_cluster_data_by_id(cluster_id: str): + _stub_time = '2021-01-01T00:00:00+08:00' + return { + 'area_id': 1, + 'artifactory': '', + 'capacity_updated_at': _stub_time, + 'cluster_id': cluster_id, + 'cluster_num': 1, + 'config_svr_count': 0, + 'created_at': _stub_time, + 'creator': 'unknown', + 'description': 'cluster description', + 'disabled': False, + 'environment': 'stag', + 'extra_cluster_id': '', + 'ip_resource_total': 0, + 'ip_resource_used': 0, + 'master_count': 0, + 'name': 'test-cluster', + 'need_nat': True, + 'node_count': 1, + 'project_id': uuid.uuid4().hex, + 'remain_cpu': 10, + 'remain_disk': 0, + 'remain_mem': 10, + 'status': 'normal', + 'total_cpu': 12, + 'total_disk': 0, + 'total_mem': 64, + 'type': 'k8s', + 'updated_at': _stub_time, + } + @staticmethod def make_project_data(project_id: str): _stub_time = '2021-01-01T00:00:00+08:00' diff --git a/bcs-app/backend/utils/error_codes.py b/bcs-app/backend/utils/error_codes.py index 90afc1069..76a600e57 100644 --- a/bcs-app/backend/utils/error_codes.py +++ b/bcs-app/backend/utils/error_codes.py @@ -70,10 +70,6 @@ class ErrorCodes: code_num=40101, status_code=status.HTTP_401_UNAUTHORIZED, ) - # 没有权限,最好使用drf permission class检查权限 - Forbidden = ErrorCode(_('没有使用权限'), code_num=40301, status_code=status.HTTP_403_FORBIDDEN) - # 权限中心错误码 - IAMCheckFailed = ErrorCode(_('权限校验失败'), code_num=40302, status_code=status.HTTP_403_FORBIDDEN) # 资源未找到 ResNotFoundError = ErrorCode(_('资源未找到'), code_num=40400, status_code=status.HTTP_404_NOT_FOUND) diff --git a/bcs-app/backend/utils/permissions.py b/bcs-app/backend/utils/permissions.py index c780a608b..21f3fc1bd 100644 --- a/bcs-app/backend/utils/permissions.py +++ b/bcs-app/backend/utils/permissions.py @@ -16,8 +16,8 @@ from rest_framework.permissions import BasePermission from backend.accounts import bcs_perm -from backend.bcs_web.iam import permissions from backend.components import paas_auth, paas_cc +from backend.iam import legacy_perms as permissions from backend.utils import FancyDict from backend.utils.cache import region from backend.utils.error_codes import error_codes diff --git a/bcs-app/poetry.lock b/bcs-app/poetry.lock index ec5e7f254..0f4aaa465 100644 --- a/bcs-app/poetry.lock +++ b/bcs-app/poetry.lock @@ -1302,6 +1302,22 @@ urllib3 = ">=1.21.1,<1.25" security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +[[package]] +name = "requests-mock" +version = "1.9.3" +description = "Mock out responses from the requests package" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=2.3,<3" +six = "*" + +[package.extras] +fixture = ["fixtures"] +test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.18)", "testtools"] + [[package]] name = "requests-oauthlib" version = "1.3.0" @@ -1698,7 +1714,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = ">=3.6.1,<3.7" -content-hash = "6263a238def4da72cfb2d5d3a7617ebabbdcdeb1289935c2beef112c981b4382" +content-hash = "1c2b95cd5bb9ad43c2f1aee35cc395c1942e917b65552cee4e38d7249fdb8b7a" [metadata.files] aiohttp = [ @@ -1837,24 +1853,36 @@ cffi = [ {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55"}, {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc"}, {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76"}, {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7"}, {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, @@ -1935,8 +1963,10 @@ cryptography = [ {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586"}, {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3"}, {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, ] curlify = [ @@ -2187,12 +2217,22 @@ mako = [ {file = "Mako-1.0.7.tar.gz", hash = "sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae"}, ] markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -2201,14 +2241,21 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -2218,6 +2265,9 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -2481,6 +2531,10 @@ requests = [ {file = "requests-2.21.0-py2.py3-none-any.whl", hash = "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"}, {file = "requests-2.21.0.tar.gz", hash = "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e"}, ] +requests-mock = [ + {file = "requests-mock-1.9.3.tar.gz", hash = "sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba"}, + {file = "requests_mock-1.9.3-py2.py3-none-any.whl", hash = "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970"}, +] requests-oauthlib = [ {file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"}, {file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"}, diff --git a/bcs-app/pyproject.toml b/bcs-app/pyproject.toml index 144b2878e..e0ebf05fc 100644 --- a/bcs-app/pyproject.toml +++ b/bcs-app/pyproject.toml @@ -68,6 +68,7 @@ mock = "3.0.5" pytest = "6.2.4" pytest-django = "3.9.0" pytest-asyncio = "^0.15.1" +requests-mock = "1.9.3" [tool.black] exclude = ''' diff --git a/bcs-app/support-files/iam/0002_project_extra.json b/bcs-app/support-files/iam/0002_project_extra.json new file mode 100644 index 000000000..fa2c39535 --- /dev/null +++ b/bcs-app/support-files/iam/0002_project_extra.json @@ -0,0 +1,44 @@ +{ + "system_id": "bk_bcs_app", + "operations": [ + { + "operation": "upsert_action", + "data": { + "id": "project_create", + "name": "项目创建", + "name_en": "create project", + "description": "用户创建项目", + "description_en": "create project", + "type": "create", + "related_resource_types": [], + "related_actions": [], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "project_delete", + "name": "项目删除", + "name_en": "delete project", + "description": "用户删除项目", + "description_en": "user delete project", + "type": "delete", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "project", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "project_list" + } + ] + }], + "version": 1 + } + } + ] +} diff --git a/bcs-app/support-files/iam/0003_cluster.json b/bcs-app/support-files/iam/0003_cluster.json new file mode 100644 index 000000000..d3e996903 --- /dev/null +++ b/bcs-app/support-files/iam/0003_cluster.json @@ -0,0 +1,156 @@ +{ + "system_id": "bk_bcs_app", + "operations": [ + { + "operation": "upsert_resource_type", + "data": { + "id": "cluster", + "name": "集群", + "name_en": "cluster", + "description": "集群", + "description_en": "cluster", + "provider_config": { + "path": "/o/bk_bcs_app/apis/iam/v1/clusters/" + }, + "version": 1 + } + }, + { + "operation": "upsert_instance_selection", + "data": { + "id": "cluster_list", + "name": "集群", + "name_en": "cluster", + "resource_type_chain": [ + {"system_id": "bk_bcs_app", "id": "project"}, + {"system_id": "bk_bcs_app", "id": "cluster"} + ] + } + }, + { + "operation": "upsert_action", + "data": { + "id": "cluster_create", + "name": "集群创建", + "name_en": "create cluster", + "description": "用户创建集群", + "description_en": "user create cluster", + "type": "create", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "project", + "related_instance_selections": [{ + "system_id": "bk_bcs_app", + "id": "project_list" + }] + }], + "related_actions": ["project_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "cluster_view", + "name": "集群查看", + "name_en": "view cluster", + "description": "用户查看集群", + "description_en": "user view cluster", + "type": "view", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "cluster", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "cluster_list" + } + ] + }], + "related_actions": ["project_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "cluster_manage", + "name": "集群管理", + "name_en": "manage cluster", + "description": "用户管理集群(包括添加/删除节点)", + "description_en": "user manage cluster(include add/delete node)", + "type": "manage", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "cluster", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "cluster_list" + } + ] + }], + "related_actions": ["project_view", "cluster_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "cluster_delete", + "name": "集群删除", + "name_en": "delete cluster", + "description": "用户删除集群", + "description_en": "user delete cluster", + "type": "delete", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "cluster", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "cluster_list" + } + ] + }], + "related_actions": ["project_view", "cluster_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "cluster_use", + "name": "集群使用", + "name_en": "use cluster", + "description": "用户使用集群(如创建命名空间等)", + "description_en": "user use cluster(e.g. create namespace)", + "type": "use", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "cluster", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "cluster_list" + } + ] + }], + "related_actions": ["project_view", "cluster_view"], + "version": 1 + } + } + ] +} diff --git a/bcs-app/support-files/iam/0004_namespace.json b/bcs-app/support-files/iam/0004_namespace.json new file mode 100644 index 000000000..22e9e1e88 --- /dev/null +++ b/bcs-app/support-files/iam/0004_namespace.json @@ -0,0 +1,157 @@ +{ + "system_id": "bk_bcs_app", + "operations": [ + { + "operation": "upsert_resource_type", + "data": { + "id": "namespace", + "name": "命名空间", + "name_en": "namespace", + "description": "命名空间", + "description_en": "namespace", + "provider_config": { + "path": "/o/bk_bcs_app/apis/iam/v1/namespaces/" + }, + "version": 1 + } + }, + { + "operation": "upsert_instance_selection", + "data": { + "id": "namespace_list", + "name": "命名空间", + "name_en": "namespace", + "resource_type_chain": [ + {"system_id": "bk_bcs_app", "id": "project"}, + {"system_id": "bk_bcs_app", "id": "cluster"}, + {"system_id": "bk_bcs_app", "id": "namespace"} + ] + } + }, + { + "operation": "upsert_action", + "data": { + "id": "namespace_create", + "name": "命名空间创建", + "name_en": "create namespace", + "description": "用户创建命名空间", + "description_en": "user create namespace", + "type": "create", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "cluster", + "related_instance_selections": [{ + "system_id": "bk_bcs_app", + "id": "cluster_list" + }] + }], + "related_actions": ["project_view", "cluster_view", "cluster_use"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "namespace_view", + "name": "命名空间查看", + "name_en": "view namespace", + "description": "用户查看命名空间", + "description_en": "user view namespace", + "type": "view", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "namespace", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "namespace_list" + } + ] + }], + "related_actions": ["project_view", "cluster_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "namespace_update", + "name": "命名空间更新", + "name_en": "update namespace", + "description": "用户更新命名空间", + "description_en": "user update namespace", + "type": "edit", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "namespace", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "namespace_list" + } + ] + }], + "related_actions": ["project_view", "cluster_view", "cluster_use","namespace_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "namespace_delete", + "name": "命名空间删除", + "name_en": "delete namespace", + "description": "用户删除命名空间", + "description_en": "user delete namespace", + "type": "delete", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "namespace", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "namespace_list" + } + ] + }], + "related_actions": ["project_view", "cluster_view", "cluster_use","namespace_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "namespace_use", + "name": "命名空间使用", + "name_en": "use namespace", + "description": "用户使用命名空间", + "description_en": "user use namespace", + "type": "use", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "namespace", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "namespace_list" + } + ] + }], + "related_actions": ["project_view", "cluster_view", "cluster_use", "namespace_view"], + "version": 1 + } + } + ] +} diff --git a/bcs-app/support-files/iam/0005_templateset.json b/bcs-app/support-files/iam/0005_templateset.json new file mode 100644 index 000000000..751b65ad2 --- /dev/null +++ b/bcs-app/support-files/iam/0005_templateset.json @@ -0,0 +1,182 @@ +{ + "system_id": "bk_bcs_app", + "operations": [ + { + "operation": "upsert_resource_type", + "data": { + "id": "templateset", + "name": "模板集", + "name_en": "templateset", + "description": "模板集", + "description_en": "templateset", + "provider_config": { + "path": "/o/bk_bcs_app/apis/iam/v1/templatesets/" + }, + "version": 1 + } + }, + { + "operation": "upsert_instance_selection", + "data": { + "id": "templateset_list", + "name": "模板集", + "name_en": "templateset", + "resource_type_chain": [ + {"system_id": "bk_bcs_app", "id": "project"}, + {"system_id": "bk_bcs_app", "id": "templateset"} + ] + } + }, + { + "operation": "upsert_action", + "data": { + "id": "templateset_create", + "name": "模板集创建", + "name_en": "create templateset", + "description": "用户创建模板集", + "description_en": "user create templateset", + "type": "create", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "project", + "related_instance_selections": [{ + "system_id": "bk_bcs_app", + "id": "project_list" + }] + }], + "related_actions": ["project_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "templateset_view", + "name": "模板集查看", + "name_en": "view templateset", + "description": "用户查看模板集", + "description_en": "user view templateset", + "type": "view", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "templateset", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "templateset_list" + } + ] + }], + "related_actions": ["project_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "templateset_copy", + "name": "模板集复制", + "name_en": "copy templateset", + "description": "用户复制模板集", + "description_en": "user copy templateset", + "type": "use", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "templateset", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "templateset_list" + } + ] + }], + "related_actions": ["project_view", "templateset_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "templateset_update", + "name": "模板集更新", + "name_en": "update templateset", + "description": "用户更新模板集", + "description_en": "user update templateset", + "type": "edit", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "templateset", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "templateset_list" + } + ] + }], + "related_actions": ["project_view", "templateset_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "templateset_delete", + "name": "模板集删除", + "name_en": "delete templateset", + "description": "用户删除模板集", + "description_en": "user delete templateset", + "type": "delete", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "templateset", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "templateset_list" + } + ] + }], + "related_actions": ["project_view", "templateset_view"], + "version": 1 + } + }, + { + "operation": "upsert_action", + "data": { + "id": "templateset_instantiate", + "name": "模板集实例化", + "name_en": "instantiate templateset", + "description": "用户实例化模板集", + "description_en": "user instantiate templateset", + "type": "use", + "related_resource_types": [{ + "system_id": "bk_bcs_app", + "id": "templateset", + "name_alias": "", + "name_alias_en": "", + "selection_mode": "instance", + "related_instance_selections": [ + { + "system_id": "bk_bcs_app", + "id": "templateset_list" + } + ] + }], + "related_actions": ["project_view", "templateset_view"], + "version": 1 + } + } + ] +} diff --git a/bcs-app/support-files/iam/0006_action_groups.json b/bcs-app/support-files/iam/0006_action_groups.json new file mode 100644 index 000000000..ccd9cec7c --- /dev/null +++ b/bcs-app/support-files/iam/0006_action_groups.json @@ -0,0 +1,96 @@ +{ + "system_id": "bk_bcs_app", + "operations": [ + { + "operation": "upsert_action_groups", + "data": [ + { + "name": "项目", + "name_en": "Project", + "actions": [ + { + "id": "project_create" + }, + { + "id": "project_view" + }, + { + "id": "project_edit" + }, + { + "id": "project_delete" + } + ], + "sub_groups": [ + { + "name": "集群", + "name_en": "Cluster", + "actions": [ + { + "id": "cluster_create" + }, + { + "id": "cluster_view" + }, + { + "id": "cluster_manage" + }, + { + "id": "cluster_delete" + }, + { + "id": "cluster_use" + } + ] + }, + { + "name": "命名空间", + "name_en": "Namespace", + "actions": [ + { + "id": "namespace_create" + }, + { + "id": "namespace_view" + }, + { + "id": "namespace_update" + }, + { + "id": "namespace_delete" + }, + { + "id": "namespace_use" + } + ] + }, + { + "name": "模板集", + "name_en": "Templateset", + "actions": [ + { + "id": "templateset_create" + }, + { + "id": "templateset_view" + }, + { + "id": "templateset_copy" + }, + { + "id": "templateset_update" + }, + { + "id": "templateset_delete" + }, + { + "id": "templateset_instantiate" + } + ] + } + ] + } + ] + } + ] +} diff --git a/bcs-app/support-files/iam/0007_resource_creator_actions.json b/bcs-app/support-files/iam/0007_resource_creator_actions.json new file mode 100644 index 000000000..a7ca53378 --- /dev/null +++ b/bcs-app/support-files/iam/0007_resource_creator_actions.json @@ -0,0 +1,80 @@ +{ + "system_id": "bk_bcs_app", + "operations": [ + { + "operation": "upsert_resource_creator_actions", + "data": { + "config":[ + { + "id":"project", + "actions":[ + { + "id":"project_edit", + "required":false + }, + { + "id":"project_view", + "required":true + } + ] + }, + { + "id":"cluster", + "actions":[ + { + "id": "cluster_use", + "required":false + }, + { + "id":"cluster_manage", + "required":false + }, + { + "id":"cluster_view", + "required":true + } + ] + }, + { + "id":"namespace", + "actions":[ + { + "id": "namespace_use", + "required":false + }, + { + "id":"namespace_update", + "required":false + }, + { + "id":"namespace_view", + "required":true + } + ] + }, + { + "id":"templateset", + "actions":[ + { + "id": "templateset_instantiate", + "required":false + }, + { + "id":"templateset_update", + "required":false + }, + { + "id":"templateset_view", + "required":true + }, + { + "id":"templateset_copy", + "required":false + } + ] + } + ] + } + } + ] +}