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

fix: 修复部分模块鉴权异常的问题 --story=119807844 #1410

Merged
merged 2 commits into from
Sep 25, 2024
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
18 changes: 15 additions & 3 deletions common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
import re
from django.conf import settings

# 开发框架公用方法
# 1. 页面输入内容转义(防止xss攻击)
Expand Down Expand Up @@ -91,5 +91,17 @@ def cmp(a, b):
return (a > b) - (a < b)


def strip_tags(value):
return re.sub(r'<[^>]*?>', '', value)
def notice_receiver_filter(receivers):
"""
通知名单过滤
"""
if not receivers:
return receivers

receiver_type = "list"
if isinstance(receivers, str):
receiver_type = "str"
receivers = receivers.strip().split(",")

receivers = [i for i in receivers if i not in settings.NOTICE_IGNORE_LIST]
return receivers if receiver_type == "list" else ",".join(receivers)
7 changes: 5 additions & 2 deletions config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import base64
import datetime
import importlib
import os
from urllib.parse import urljoin, urlparse

from blueapps.conf.default_settings import * # noqa
Expand Down Expand Up @@ -959,5 +960,7 @@ def redirect_func(request):
BK_SHARED_RES_URL = os.getenv("BKPAAS_SHARED_RES_URL") or os.getenv("BKAPP_SHARED_RES_URL")
BK_PLATFORM_NAME = os.getenv("BKAPP_PLATFORM_NAME", "")

# 本地开发环境跳转豁免配置
BKAPP_SKIP_SECURE = os.getenv("BKAPP_SKIP_SECURE", False)
# 通知过滤
NOTICE_IGNORE_LIST = os.getenv("BKAPP_NOTICE_IGNORE_LIST", [])
if isinstance(NOTICE_IGNORE_LIST, str):
NOTICE_IGNORE_LIST = [i.lower().strip() for i in NOTICE_IGNORE_LIST.split(",")]
3 changes: 3 additions & 0 deletions config/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@

MEDIA_URL = "%smedia/" % SITE_URL

# IAM
IAM_SKIP_AUTH = os.getenv("BKAPP_IAM_SKIP_AUTH", False)

# ==============================================================================
# 加载环境差异化配置
# ==============================================================================
Expand Down
1 change: 1 addition & 0 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
【修复】修复单据列表在非首页搜索报错的问题
【修复】任务模板编辑点击上一步返回路径优化
【修复】tooltips组件XSS问题修复
【修复】修复部分模块鉴权异常的问题

## [Version: 2.6.30] - 2024-08-15
【修复】项目初始化时登录后重定向url异常修复
Expand Down
2 changes: 2 additions & 0 deletions docs/RELEASE_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
【Fixed】Fixed the issue where searching on the document list in non-home pages resulted in an error.
【Fixed】Optimized the return path when clicking ‘Previous’ during task template editing.
【Fixed】Fixed XSS vulnerability in tooltips component.
【Fixed】Fix the issue with authentication anomalies in certain modules.


## [Version: 2.6.30] - 2024-08-15
【Fixed】Fixed the URL Redirection Issue after Login during Project Initialization
Expand Down
21 changes: 21 additions & 0 deletions iam/contrib/iam_migration/migrations/0012_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-

from django.db import migrations
from django.conf import settings

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 = "initial.json"
if settings.IAM_INITIAL_FILE == "dev":
migration_json = "initial_dev.json"

dependencies = [("iam_migration", "0011_update")]

operations = [migrations.RunPython(forward_func)]
2 changes: 1 addition & 1 deletion itsm/auth_iam/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def batch_resource_multi_actions_allowed(
获取批量资源的权限
:return:
"""
if settings.ENVIRONMENT == "dev":
if settings.ENVIRONMENT == "dev" and settings.IAM_SKIP_AUTH:
# dev 环境不走权限中心
actions_result = {action: True for action in actions}
return {
Expand Down
7 changes: 7 additions & 0 deletions itsm/component/bkchat/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.http import JsonResponse

from common.log import logger
from common.utils import notice_receiver_filter
from config.default import CLOSE_NOTIFY
from itsm.component.constants import APPROVE_RESULT, API, RUNNING, SHOW_BY_CONDITION
from itsm.component.exceptions import ComponentCallError
Expand Down Expand Up @@ -112,6 +113,12 @@ def send_fast_approval_message(title, content, receivers, ticket, state_id):

# 更新详情url
ticket.generate_ticket_url(state_id, receivers)

# 接收人过滤
receivers = notice_receiver_filter(receivers)
if not receivers:
logger.info(f"[fast approval] receivers is empty after filter, ticket_id=>{ticket_id}")
return

# 构造data信息
data = {
Expand Down
4 changes: 2 additions & 2 deletions itsm/component/constants/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@
},
{
"id": "system_settings_manage",
"name": _("系统配置管理"),
"name": _("项目管理"),
"relate_resources": ["project"],
"relate_actions": ["project_view"],
"resource_topo": ["project", "system_settings"],
"resource_topo": ["project"],
},
{
"id": "ticket_view",
Expand Down
2 changes: 1 addition & 1 deletion itsm/component/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class Model(models.Model):
_objects = models.Manager()
objects = managers.Manager()

resource_operations = ["flow_element_manage"]
resource_operations = ["system_settings_manage"]

class Meta:
app_label = "postman"
Expand Down
64 changes: 44 additions & 20 deletions itsm/component/drf/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,37 +89,52 @@ class IamAuthPermit(permissions.BasePermission):

def has_permission(self, request, view):
# 不关联实例的资源,任何请求都要提前鉴权
# 当前系统内,如果没有project_view的权限,无法进入系统
if view.action in getattr(view, "permission_free_actions", []):
return True

# apply_actions = ["project_view"]
apply_actions = []
resource_type = getattr(view.queryset.model, "auth_resource", {}).get(
"resource_type"
)

if view.action == "create":
apply_actions.append("{}_create".format(resource_type))
apply_actions = self.get_view_iam_actions(view)

# 项目下创建资源
if view.action in ["create", "imports"]:
if not apply_actions:
resource_type = getattr(view.queryset.model, "auth_resource", {}).get(
"resource_type"
)
apply_actions.append("{}_create".format(resource_type))
if "project_key" in request.data:
return self.iam_create_auth(request, apply_actions)

return True

def has_object_permission(self, request, view, obj, **kwargs):
# 关联实例的请求,需要针对对象进行鉴权
if view.action in getattr(view, "permission_free_actions", []):
return True

apply_actions = obj.resource_operations

# 获取视图权限action
apply_actions = self.get_view_iam_actions(view)
return self.iam_auth(request, apply_actions, obj)

@staticmethod
def get_view_iam_actions(view):
# 获取视图权限action
apply_actions = []
if hasattr(view, "permission_action_mapping"):
apply_actions = view.permission_action_mapping.get(view.action)

# 默认动作
if not apply_actions and hasattr(view, "permission_action_default"):
apply_actions = view.permission_action_default

if isinstance(apply_actions, str):
apply_actions = [apply_actions]
return apply_actions

def iam_auth(self, request, apply_actions, obj=None):

resources = []
if obj:
if isinstance(obj, Project):
resource_id = (str(getattr(obj, "key")),)
resource_id = str(getattr(obj, "key"))
else:
resource_id = str(getattr(obj, "id"))

Expand All @@ -144,10 +159,12 @@ def iam_auth(self, request, apply_actions, obj=None):
if resources:
if hasattr(obj, "project_key"):
project_key = getattr(obj, "project_key")
elif isinstance(obj, Project):
project_key = str(getattr(obj, "key"))
auth_actions = iam_client.batch_resource_multi_actions_allowed(
set(apply_actions), resources, project_key=project_key
)
auth_actions = auth_actions.get(resources[0]["resource_id"], {})
auth_actions = auth_actions.get(str(resources[0]["resource_id"]), {})
else:
auth_actions = iam_client.resource_multi_actions_allowed(apply_actions, [])

Expand Down Expand Up @@ -240,11 +257,12 @@ def auth_result(auth_actions, actions):
"""
认证结果解析
"""
denied_actions = []
for action, result in auth_actions.items():
if action in actions and result is False:
denied_actions.append(action)
return len(denied_actions) == 0
for action in actions:
if action not in auth_actions:
return False
elif auth_actions[action] is False:
return False
return True

@staticmethod
def is_safe_method(request, view):
Expand Down Expand Up @@ -279,9 +297,15 @@ def has_object_permission(self, request, view, obj):

class IamAuthProjectViewPermit(IamAuthPermit):
def has_object_permission(self, request, view, obj):
apply_actions = self.get_view_iam_actions(view)

if hasattr(obj, "project_key"):
project_key = obj.project_key
apply_actions = ["project_view"]
if not apply_actions and view.action in ["create", "update", "destroy"]:
apply_actions = ["system_settings_manage"]

# 项目管理必须有查看权限
apply_actions.append("project_view")
return self.has_project_view_permission(request, project_key, apply_actions)

return True
Expand Down
7 changes: 3 additions & 4 deletions itsm/component/misc_middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,10 @@ def __init__(self, redirect_to, *args, **kwargs):

class HttpsMiddleware(MiddlewareMixin):
def process_request(self, request):
if settings.ENVIRONMENT == "dev":
return None

if settings.RUN_VER == "ieod":
# 本地开发环境跳转豁免
if settings.BKAPP_SKIP_SECURE:
return None

# 对于openapi 跳转豁免
if request.path.startswith(EXEMPT_HTTPS_REDIRECT):
return None
Expand Down
3 changes: 2 additions & 1 deletion itsm/component/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from django.utils.translation import ugettext as _

from common.log import logger
from common.utils import notice_receiver_filter
from itsm.component.constants import GENERAL_NOTICE
from itsm.component.esb.esbclient import client_backend
from itsm.component.exceptions import ComponentCallError
Expand All @@ -39,7 +40,7 @@
class BaseNotifier(object):
def __init__(self, title, receivers, message, notify_type=GENERAL_NOTICE):
self.title = title
self.receivers = receivers
self.receivers = notice_receiver_filter(receivers)
self.message = message
self.notify_type = notify_type

Expand Down
8 changes: 4 additions & 4 deletions itsm/iadmin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ class CustomNotice(models.Model):
_("项目key"), max_length=LEN_SHORT, null=False, default=PUBLIC_PROJECT_PROJECT_KEY
)

auth_resource = {"resource_type": "flow_element", "resource_type_name": "流程元素"}
resource_operations = ["flow_element_manage"]
auth_resource = {"resource_type": "project", "resource_type_name": "项目"}
resource_operations = ["system_settings_manage"]

class Meta:
app_label = "iadmin"
Expand Down Expand Up @@ -335,8 +335,8 @@ class Data(models.Model):

objects = DataManager()

auth_resource = {"resource_type": "flow_element", "resource_type_name": "流程元素"}
resource_operations = ["flow_element_manage"]
auth_resource = {"resource_type": "project", "resource_name": _("项目")}
resource_operations = ["system_settings_manage"]

class Meta:
app_label = "iadmin"
Expand Down
46 changes: 34 additions & 12 deletions itsm/iadmin/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
from rest_framework import permissions

from common.mymako import render_mako
from itsm.component.drf.permissions import IamAuthWithoutResourcePermit
from itsm.component.constants import PUBLIC_PROJECT_PROJECT_KEY
from itsm.component.drf.permissions import IamAuthWithoutResourcePermit, IamAuthPermit
from itsm.project.models import Project
from itsm.role.models import UserRole


Expand Down Expand Up @@ -131,18 +133,38 @@ def has_permission(self, request, view):
return self.iam_auth(request, apply_actions)


class CustomNotifyPermit(IamAuthWithoutResourcePermit):
class CustomNotifyPermit(IamAuthPermit):
def has_permission(self, request, view):
if view.action in ["variable_list", "action_type"]:
if view.action in getattr(view, "permission_free_actions", []):
return True
apply_actions = []
if view.action == "list":
if "project_key" not in request.query_params:
apply_actions = ["notification_view", "platform_manage_access"]
return self.iam_auth(request, apply_actions)

def has_object_permission(self, request, view, obj):
if obj.project_key == "public":

# 获取项目标识
if view.action in ["list"]:
project_key = request.query_params.get("project_key", PUBLIC_PROJECT_PROJECT_KEY)
elif view.action in ["destroy"]:
instance = view.get_object()
project_key = instance.project_key
else:
project_key = request.data.get("project_key", PUBLIC_PROJECT_PROJECT_KEY)

# 平台管理
if project_key == PUBLIC_PROJECT_PROJECT_KEY:
apply_actions = ["notification_view", "platform_manage_access"]
return self.iam_auth(request, apply_actions)

# 项目管理
project = Project.objects.get(pk=project_key)
apply_actions = ["system_settings_manage"]
return self.iam_auth(request, apply_actions, project)

def has_object_permission(self, request, view, obj, **kwargs):
# 平台管理:通知配置
if obj.project_key == PUBLIC_PROJECT_PROJECT_KEY:
apply_actions = ["notification_view", "platform_manage_access"]
if view.action in ["update", "delete"]:
apply_actions.append("notification_manage")
return self.iam_auth(request, apply_actions)
return True

# 项目:通知配置
project = Project.objects.filter(pk=obj.project_key).first()
return super().has_object_permission(request, view, project, **kwargs)
3 changes: 3 additions & 0 deletions itsm/iadmin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ class CustomNotifyViewSet(ModelViewSet):
serializer_class = CustomNotifySerializer
queryset = CustomNotice.objects.all()
pagination_class = None

permission_classes = (CustomNotifyPermit,)
permission_free_actions = ["variable_list", "action_type"]
permission_action_default = "system_settings_manage"

filter_fields = {
"notify_type": ["exact"],
Expand Down
1 change: 0 additions & 1 deletion itsm/openapi/workflow/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ def clean_states(self, states):
'end_at',
'update_at',
'notify',
'extras',
'updated_by',
'service',
'axis',
Expand Down
Loading
Loading