Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add user perms & view decorator response_perms #1599

Merged
merged 5 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bcs-app/backend/components/paas_cc.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ def __init__(self, host: str):
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}}/"
self.list_clusters_url = f"{host}/cluster_list/"
self.update_node_list_url = f"{host}/projects/{{project_id}}/clusters/{{cluster_id}}/nodes/"
self.get_cluster_namespace_list_url = f"{host}/projects/{{project_id}}/clusters/{{cluster_id}}/namespaces/"
self.get_node_list_url = f"{host}/projects/{{project_id}}/nodes/"
Expand Down Expand Up @@ -559,6 +560,13 @@ def get_cluster_by_id(self, cluster_id: str) -> Dict:
url = self._config.get_cluster_by_id_url.format(cluster_id=cluster_id)
return self._client.request_json('GET', url)

@response_handler()
def list_clusters(self, cluster_ids: List[str]) -> Dict:
"""根据集群ID列表批量获取集群信息"""
url = self._config.list_clusters_url
data = {"cluster_ids": cluster_ids}
return self._client.request_json('POST', url, json=data)

@parse_response_data()
def get_project(self, project_id: str) -> Dict:
"""获取项目信息"""
Expand Down
12 changes: 3 additions & 9 deletions bcs-app/backend/helm/helm/utils/repo_bk.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
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 base64
import hashlib
import logging

import requests
import yaml

from backend.utils.basic import md5_digest

logger = logging.getLogger(__name__)


Expand All @@ -36,12 +36,6 @@ def make_requests_auth(auth):
raise NotImplementedError(auth["type"])


def _md5(content):
h = hashlib.md5()
h.update(content.encode("utf-8"))
return h.hexdigest()


def get_charts_info(url, auths):
url = url.rstrip("/")
req_charts_url = "{url}/index.yaml".format(url=url)
Expand All @@ -62,7 +56,7 @@ def get_charts_info(url, auths):
return (False, None, None)

# 生成MD5,主要是便于后续校验是否变动
charts_info_hash = _md5(str(charts_info))
charts_info_hash = md5_digest(str(charts_info))
return (True, charts_info, charts_info_hash)


Expand Down
69 changes: 69 additions & 0 deletions bcs-app/backend/iam/open_apis/providers/cluster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- 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 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 backend.container_service.clusters.base import get_clusters

from .utils import get_system_token

logger = logging.getLogger(__name__)


class ClusterProvider(ResourceProvider):
"""集群 Provider"""

def list_instance(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult:
"""
获取集群列表

:param filter_obj: 查询参数字典。 以下为必传 如: {"parent": {"id": 1}}
:param page_obj: 分页对象
:return: ListResult 类型的实例列表
"""
project_id = filter_obj.parent['id']
cluster_list = get_clusters(get_system_token(), project_id)
results = [
{'id': cluster['cluster_id'], 'display_name': cluster['name']}
for cluster in cluster_list[page_obj.slice_from : page_obj.slice_to]
]
return ListResult(results=results, count=len(cluster_list))

def fetch_instance_info(self, filter_obj: FancyDict, **options) -> ListResult:
"""
批量获取集群属性详情

:param filter_obj: 查询参数字典
:return: ListResult 类型的实例列表
"""
cluster_ids = filter_obj.ids
paas_cc = PaaSCCClient(auth=ComponentAuth(get_system_token()))
cluster_list = paas_cc.list_clusters(cluster_ids)
results = [{'id': cluster['cluster_id'], 'display_name': cluster['name']} for cluster in cluster_list]
return ListResult(results=results, count=len(results))

def list_instance_by_policy(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult:
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)
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from backend.components.base import ComponentAuth
from backend.components.paas_cc import PaaSCCClient
from backend.iam.permissions.resources.namespace import calc_iam_ns_id

from .utils import get_system_token

Expand All @@ -31,30 +32,26 @@ def list_instance(self, filter_obj: FancyDict, page_obj: Page, **options) -> Lis
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]
results = [
{'id': calc_iam_ns_id(cluster_id, ns['name']), 'display_name': ns['name']}
for ns in namespace_list[page_obj.slice_from : page_obj.slice_to]
]

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)
iam_cluster_ns = self._calc_iam_cluster_ns(filter_obj.ids)

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]
results = []
for iam_ns_id in filter_obj.ids:
# 如果 iam_cluster_ns 没有对应的 iam_ns_id, 则不添加到结果表中
name = iam_cluster_ns.get(iam_ns_id)
if name:
results.append({'id': iam_ns_id, 'display_name': name})

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:
Expand All @@ -63,6 +60,22 @@ def list_attr(self, **options) -> ListResult:
def list_attr_value(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult:
return ListResult(results=[], count=0)

def _calc_iam_cluster_ns(self, iam_ns_ids: List[str]) -> Dict[str, str]:
"""
计算出 iam_ns_id 和命名空间名称的映射表

:param iam_ns_ids: iam_ns_id 列表。iam_ns_id 的计算规则查看 calc_iam_ns_id 函数的实现
:return 映射表 {iam_ns_id: 命名空间名}, 如 {'40000:70815bb9te': 'test-default'}
"""
iam_cluster_ns = {}
cluster_set = {f"BCS-K8S-{iam_ns_id.split(':')[0]}" for iam_ns_id in iam_ns_ids}

for cluster_id in cluster_set:
for ns in self._list_namespaces(cluster_id):
iam_cluster_ns[calc_iam_ns_id(cluster_id, ns['name'])] = ns['name']

return iam_cluster_ns

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@


class ProjectProvider(ResourceProvider):
"""项目资源的 Provider"""
"""项目 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]
results = [
{'id': p['project_id'], 'display_name': p['project_name']}
for p in projects[page_obj.slice_from : page_obj.slice_to]
]
return ListResult(results=results, count=len(projects))

def fetch_instance_info(self, filter_obj: FancyDict, **options) -> ListResult:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@
from iam.resource.utils import Page, get_filter_obj, get_page_obj

from ..constants import ResourceType
from .cluster import ClusterProvider
from .namespace import NamespaceProvider
from .project import ProjectProvider
from .templateset import TemplatesetProvider

PROVIDER_CLS_MAP = {ResourceType.Project: ProjectProvider, ResourceType.Namespace: NamespaceProvider}
PROVIDER_CLS_MAP = {
ResourceType.Project: ProjectProvider,
ResourceType.Namespace: NamespaceProvider,
ResourceType.Cluster: ClusterProvider,
ResourceType.Templateset: TemplatesetProvider,
}


class ResourceProvider:
Expand All @@ -32,16 +39,20 @@ def __init__(self, resource_type: str):
def provide(self, method: str, data: Dict, **options) -> Union[List, Dict]:
"""
根据 method 值, 调用对应的方法返回数据

:param method: 值包括 list_attr, list_attr_value, list_instance 等
:param data: 其他查询条件数据,如分页数据等
:return: method 方法返回的数据
"""
handler = getattr(self, method)
return handler(data, **options)

def list_attr(self, data: Optional[Dict] = None, **options) -> List[Dict]:
"""
查询某个资源类型可用于配置权限的属性列表
:param data: 占位字段,为了上面provide方法的处理统一

:param data: 占位参数,为了 self.provide 方法的处理统一
:return: 属性列表
"""
result = self.resource_provider.list_attr(**options)
return result.to_list()
Expand Down
58 changes: 58 additions & 0 deletions bcs-app/backend/iam/open_apis/providers/templateset.py
Original file line number Diff line number Diff line change
@@ -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 iam.collection import FancyDict
from iam.resource.provider import ListResult, ResourceProvider
from iam.resource.utils import Page

from backend.templatesets.legacy_apps.configuration.models import Template


class TemplatesetProvider(ResourceProvider):
"""模板集 Provider"""

def list_instance(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult:
"""
获取模板集列表

:param filter_obj: 查询参数。 以下为必传如: {"parent": {"id": 1}}
:param page_obj: 分页对象
:return: ListResult 类型的实例列表
"""
template_qset = Template.objects.filter(project_id=filter_obj.parent['id']).values('id', 'name')
results = [
{'id': template['id'], 'display_name': template['name']}
for template in template_qset[page_obj.slice_from : page_obj.slice_to]
]
return ListResult(results=results, count=template_qset.count())

def fetch_instance_info(self, filter_obj: FancyDict, **options) -> ListResult:
"""
批量获取模板集属性详情

:param filter_obj: 查询参数
:return: ListResult 类型的实例列表
"""
template_qset = Template.objects.filter(id__in=filter_obj.ids).values('id', 'name')
results = [{'id': template['id'], 'display_name': template['name']} for template in template_qset]
return ListResult(results=results, count=template_qset.count())

def list_instance_by_policy(self, filter_obj: FancyDict, page_obj: Page, **options) -> ListResult:
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)
2 changes: 1 addition & 1 deletion bcs-app/backend/iam/open_apis/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from .. import views

resource_types = 'projects|namespaces'
resource_types = 'projects|namespaces|clusters|templatesets'

urlpatterns = [
url(r'^(?P<resource_type>{})/'.format(resource_types), views.ResourceAPIView.as_view()),
Expand Down
2 changes: 1 addition & 1 deletion bcs-app/backend/iam/open_apis/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from backend.utils.renderers import BKAPIRenderer

from .authentication import IamBasicAuthentication
from .provider.resource import ResourceProvider
from .providers.resource import ResourceProvider
from .serializers import QueryResourceSLZ


Expand Down
44 changes: 44 additions & 0 deletions bcs-app/backend/iam/perm_maker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# -*- 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 django.utils.module_loading import import_string

from .permissions.exceptions import AttrValidationError
from .permissions.perm import PermCtx, Permission

logger = logging.getLogger(__name__)


def make_perm_ctx(res_type: str, username: str = 'anonymous', **ctx_kwargs) -> PermCtx:
"""根据资源类型,生成对应的 PermCtx"""
p_module_name = __name__.rsplit('.', 1)[0]
perm_ctx_cls = import_string(f'{p_module_name}.permissions.resources.{res_type.capitalize()}PermCtx')

try:
perm_ctx = perm_ctx_cls(username=username, **ctx_kwargs)
except TypeError as e:
logger.error(e)
raise AttrValidationError("perm ctx got an unexpected init argument")

perm_ctx.validate()
return perm_ctx


def make_res_permission(res_type: str) -> Permission:
"""根据资源类型,生成对应的 Permission"""
p_module_name = __name__.rsplit('.', 1)[0]
perm_cls = import_string(f'{p_module_name}.permissions.resources.{res_type.capitalize()}Permission')
return perm_cls()
Loading