-
Notifications
You must be signed in to change notification settings - Fork 19
feat: 凭证管理增强 --story=125449007 #548
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
base: master
Are you sure you want to change the base?
Changes from all commits
52ebd1a
c22ef0d
323ebd2
0ab0e1c
cfc6aff
ef19a9f
cf51fac
d2f531a
c44773c
4bb7eab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,3 @@ | ||
| # -*- coding: utf-8 -*- | ||
| """ | ||
| TencentBlueKing is pleased to support the open source community by making | ||
| 蓝鲸流程引擎服务 (BlueKing Flow Engine Service) available. | ||
|
|
@@ -20,28 +19,92 @@ | |
| from django.utils.translation import ugettext_lazy as _ | ||
| from rest_framework import serializers | ||
|
|
||
| from bkflow.space.credential import BkAppCredential | ||
| from bkflow.space.credential import CredentialDispatcher | ||
| from bkflow.space.models import Credential, CredentialScopeLevel | ||
| from bkflow.space.serializers import CredentialScopeSerializer | ||
|
|
||
|
|
||
| class CredentialSerializer(serializers.ModelSerializer): | ||
| create_at = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S") | ||
| update_at = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S") | ||
|
|
||
| def to_representation(self, instance): | ||
| data = super().to_representation(instance) | ||
| credential = CredentialDispatcher(credential_type=instance.type, data=instance.content) | ||
| if credential: | ||
| data["content"] = credential.display_value() | ||
| else: | ||
| data["content"] = {} | ||
|
|
||
| return data | ||
|
|
||
| class Meta: | ||
| model = Credential | ||
| fields = "__all__" | ||
|
|
||
|
|
||
| class CreateCredentialSerializer(serializers.Serializer): | ||
| name = serializers.CharField(help_text=_("凭证名称"), max_length=32, required=True) | ||
| desc = serializers.CharField(help_text=_("凭证描述"), max_length=32, required=False) | ||
| desc = serializers.CharField(help_text=_("凭证描述"), max_length=128, required=False) | ||
| type = serializers.CharField(help_text=_("凭证类型"), max_length=32, required=True) | ||
| content = serializers.JSONField(help_text=_("凭证内容"), required=True) | ||
| scope_level = serializers.ChoiceField( | ||
| help_text=_("作用域级别"), | ||
| required=False, | ||
| default=CredentialScopeLevel.NONE.value, | ||
| choices=Credential.CREDENTIAL_SCOPE_LEVEL_CHOICES, | ||
| ) | ||
| scopes = serializers.ListField( | ||
| child=CredentialScopeSerializer(), help_text=_("凭证作用域列表"), required=False, default=list | ||
| ) | ||
|
|
||
| def validate(self, attrs): | ||
| # 动态验证content根据type | ||
| credential_type = attrs.get("type") | ||
| content = attrs.get("content") | ||
|
|
||
| def validate_content(self, value): | ||
| content_ser = BkAppCredential.BkAppSerializer(data=value) | ||
| content_ser.is_valid(raise_exception=True) | ||
| return value | ||
| if attrs.get("scope_level") == CredentialScopeLevel.PART.value and not attrs.get("scopes"): | ||
| raise serializers.ValidationError(_("作用域不能为空")) | ||
|
|
||
| try: | ||
| credential = CredentialDispatcher(credential_type, data=content) | ||
| credential.validate_data() | ||
| except Exception as e: | ||
| raise serializers.ValidationError({"content": str(e)}) | ||
Check warningCode scanning / CodeQL Information exposure through an exception Medium Stack trace information Error loading related location Loading |
||
|
|
||
| return attrs | ||
|
|
||
|
|
||
| class UpdateCredentialSerializer(serializers.Serializer): | ||
| name = serializers.CharField(help_text=_("凭证名称"), max_length=32, required=False) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| desc = serializers.CharField(help_text=_("凭证描述"), max_length=32, required=False) | ||
| desc = serializers.CharField(help_text=_("凭证描述"), max_length=128, required=False) | ||
| type = serializers.CharField(help_text=_("凭证类型"), max_length=32, required=False) | ||
| content = serializers.JSONField(help_text=_("凭证内容"), required=False) | ||
| scope_level = serializers.ChoiceField( | ||
| help_text=_("作用域级别"), | ||
| required=False, | ||
| default=CredentialScopeLevel.NONE.value, | ||
| choices=Credential.CREDENTIAL_SCOPE_LEVEL_CHOICES, | ||
| ) | ||
| scopes = serializers.ListField(child=CredentialScopeSerializer(), help_text=_("凭证作用域列表"), required=False) | ||
|
|
||
| def validate(self, attrs): | ||
| if attrs.get("scope_level") == CredentialScopeLevel.PART.value and not attrs.get("scopes"): | ||
| raise serializers.ValidationError(_("作用域不能为空")) | ||
|
|
||
| # 如果提供了type和content,需要验证content | ||
| if "content" in attrs: | ||
| # 如果有type字段使用type,否则需要从实例获取 | ||
| credential_type = attrs.get("type") | ||
| if not credential_type and hasattr(self, "instance"): | ||
| credential_type = self.instance.type | ||
|
|
||
| if credential_type: | ||
| content = attrs.get("content") | ||
| try: | ||
| credential = CredentialDispatcher(credential_type, data=content) | ||
| credential.validate_data() | ||
| except Exception as e: | ||
| raise serializers.ValidationError({"content": str(e)}) | ||
Check warningCode scanning / CodeQL Information exposure through an exception Medium Stack trace information Error loading related location Loading |
||
|
|
||
| def validate_content(self, value): | ||
| content_ser = BkAppCredential.BkAppSerializer(data=value) | ||
| content_ser.is_valid(raise_exception=True) | ||
| return value | ||
| return attrs | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,3 @@ | ||
| # -*- coding: utf-8 -*- | ||
| """ | ||
| TencentBlueKing is pleased to support the open source community by making | ||
| 蓝鲸流程引擎服务 (BlueKing Flow Engine Service) available. | ||
|
|
@@ -21,12 +20,13 @@ | |
|
|
||
| from apigw_manager.apigw.decorators import apigw_require | ||
| from blueapps.account.decorators import login_exempt | ||
| from django.db import transaction | ||
| from django.views.decorators.csrf import csrf_exempt | ||
| from django.views.decorators.http import require_POST | ||
|
|
||
| from bkflow.apigw.decorators import check_jwt_and_space, return_json_response | ||
| from bkflow.apigw.serializers.credential import CreateCredentialSerializer | ||
| from bkflow.space.models import Credential | ||
| from bkflow.space.models import Credential, CredentialScope, CredentialScopeLevel | ||
|
|
||
|
|
||
| @login_exempt | ||
|
|
@@ -36,9 +36,39 @@ | |
| @check_jwt_and_space | ||
| @return_json_response | ||
| def create_credential(request, space_id): | ||
| """ | ||
| 创建凭证 | ||
|
|
||
| :param request: HTTP 请求对象 | ||
| :param space_id: 空间ID | ||
| :return: 创建的凭证信息 | ||
| """ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 语法错误: 此行代码不完整 |
||
| data = json.loads(request.body) | ||
| ser = CreateCredentialSerializer(data=data) | ||
| ser.is_valid(raise_exception=True) | ||
| # 序列化器已经检查过是否存在了 | ||
| credential = Credential.create_credential(**ser.data, space_id=space_id, creator=request.user.username) | ||
|
|
||
| # 提取作用域数据 | ||
| credential_data = dict(ser.validated_data) | ||
| scopes = credential_data.pop("scopes", []) | ||
| scope_level = credential_data.pop("scope_level", None) | ||
|
|
||
| # 创建凭证和作用域 | ||
| with transaction.atomic(): | ||
| # 序列化器已经检查过是否存在了 | ||
| credential = Credential.create_credential( | ||
| **credential_data, space_id=space_id, creator=request.user.username, scope_level=scope_level | ||
| ) | ||
|
|
||
| # 创建凭证作用域 | ||
| if scope_level == CredentialScopeLevel.PART.value and scopes: | ||
| scope_objects = [ | ||
| CredentialScope( | ||
| credential_id=credential.id, | ||
| scope_type=scope.get("scope_type"), | ||
| scope_value=scope.get("scope_value"), | ||
| ) | ||
| for scope in scopes | ||
| ] | ||
| CredentialScope.objects.bulk_create(scope_objects) | ||
|
|
||
| return credential.display_json() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| """ | ||
| TencentBlueKing is pleased to support the open source community by making | ||
| 蓝鲸流程引擎服务 (BlueKing Flow Engine Service) available. | ||
| Copyright (C) 2024 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. | ||
|
|
||
| We undertake not to change the open source license (MIT license) applicable | ||
|
|
||
| to the current version of the project delivered to anyone in the future. | ||
| """ | ||
| import json | ||
|
|
||
| from apigw_manager.apigw.decorators import apigw_require | ||
| from blueapps.account.decorators import login_exempt | ||
| from django.db import transaction | ||
| from django.utils.translation import ugettext_lazy as _ | ||
| from django.views.decorators.csrf import csrf_exempt | ||
| from django.views.decorators.http import require_http_methods | ||
|
|
||
| from bkflow.apigw.decorators import check_jwt_and_space, return_json_response | ||
| from bkflow.apigw.serializers.credential import UpdateCredentialSerializer | ||
| from bkflow.exceptions import ValidationError | ||
| from bkflow.space.models import Credential, CredentialScope | ||
|
|
||
|
|
||
| @login_exempt | ||
| @csrf_exempt | ||
| @require_http_methods(["PUT", "PATCH"]) | ||
| @apigw_require | ||
| @check_jwt_and_space | ||
| @return_json_response | ||
| def update_credential(request, space_id, credential_id): | ||
| """ | ||
| 更新凭证 | ||
|
|
||
| :param request: HTTP 请求对象 | ||
| :param space_id: 空间ID | ||
| :param credential_id: 凭证ID | ||
| :return: 更新后的凭证信息 | ||
| """ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| data = json.loads(request.body) | ||
| ser = UpdateCredentialSerializer(data=data) | ||
| ser.is_valid(raise_exception=True) | ||
|
|
||
| try: | ||
| credential = Credential.objects.get(id=credential_id, space_id=space_id, is_deleted=False) | ||
| except Credential.DoesNotExist: | ||
| raise ValidationError(_("凭证不存在: space_id={}, credential_id={}").format(space_id, credential_id)) | ||
|
|
||
| with transaction.atomic(): | ||
| # 更新凭证基本信息 | ||
| credential_data = dict(ser.validated_data) | ||
| scopes_data = credential_data.pop("scopes", None) | ||
|
|
||
| for attr, value in credential_data.items(): | ||
| if attr == "content": | ||
| # 使用update_credential方法来更新content,会做验证 | ||
| credential.update_credential(value) | ||
| else: | ||
| setattr(credential, attr, value) | ||
|
|
||
| credential.updated_by = request.user.username | ||
| credential.save() | ||
|
|
||
| # 更新凭证作用域 | ||
| if scopes_data is not None: | ||
| # 删除旧的作用域 | ||
| CredentialScope.objects.filter(credential_id=credential.id).delete() | ||
| # 创建新的作用域 | ||
| if scopes_data: | ||
| scope_objects = [ | ||
| CredentialScope( | ||
| credential_id=credential.id, | ||
| scope_type=scope.get("scope_type"), | ||
| scope_value=scope.get("scope_value"), | ||
| ) | ||
| for scope in scopes_data | ||
| ] | ||
| CredentialScope.objects.bulk_create(scope_objects) | ||
|
|
||
| return credential.display_json() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
except Exception会捕获所有异常,包括系统错误。建议改为具体异常类型如except ValidationError