Compare commits

...

22 Commits
v4.7 ... v2.20

Author SHA1 Message Date
feng626
bb163b088f fix: 修改api doc bug 2022-04-21 10:51:11 +08:00
halo
d5e7f06653 fix: ftp日志清理bug 2022-04-19 17:44:21 +08:00
feng626
9560c4b05d fix: 修复用户数据不同步问题 2022-04-08 15:44:10 +08:00
Jiangjie.Bai
81a5febfbc fix: 添加用户不能自更新字段逻辑 & 修复用户is_active创建失败的问题
fix: 添加用户不能自更新字段逻辑 & 修复用户is_active创建失败的问题

fix: 添加用户不能自更新字段逻辑 & 修复用户is_active创建失败的问题
2022-03-30 19:45:31 +08:00
ibuler
a09a59e7b3 perf: 优化 perms 缓存刷新 2022-03-22 18:58:30 +08:00
Jiangjie.Bai
a7d0e3ce70 fix: 修复获取远程应用认证信息问题 2022-03-22 18:54:50 +08:00
ibuler
6f762d7198 fix: 修复权限 view 没有 Model 的问题 2022-03-22 16:57:00 +08:00
feng626
ddbcbb0d66 fix: 修复用户绑定角色重大bug 2022-03-22 16:55:15 +08:00
feng626
a1976e4f0b fix: api docs 2022-03-22 13:05:27 +08:00
ibuler
0eeb1e6f6d perf: 去掉导入的 trans 2022-03-21 19:25:51 +08:00
ibuler
ae5487720c perf: 去掉注释 2022-03-21 19:25:51 +08:00
ibuler
0d8386359e perf: 修复创建 superuser 错误 2022-03-21 19:25:51 +08:00
fit2bot
d338e53862 fix: 修复wateway api (#7948)
Co-authored-by: feng626 <1304903146@qq.com>
2022-03-21 17:52:27 +08:00
Jiangjie.Bai
441c6a7f60 fix: 修复用户API权限 2022-03-21 11:59:59 +08:00
Jiangjie.Bai
79a40f7ded fix: 修复用户API权限 2022-03-21 11:56:50 +08:00
fit2bot
b9c221c856 fix: 修复api docs打不开的问题 (#7939)
Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>
2022-03-21 11:14:29 +08:00
Jiangjie.Bai
f6dada03bf fix: 应用树隐藏mongodb节点 2022-03-18 18:02:39 +08:00
fit2bot
d3d18b8f48 perf: org del ticket perm (#7933)
Co-authored-by: feng626 <1304903146@qq.com>
2022-03-18 17:44:39 +08:00
Jiangjie.Bai
a2f23c2d81 fix: 工单权限位放到sys角色中 2022-03-18 17:36:03 +08:00
Jiangjie.Bai
f0df23a15a fix: 移除权限dashboard 2022-03-18 17:10:17 +08:00
fit2bot
a6a4bb1c7d fix: apikey perm (#7919)
Co-authored-by: feng626 <1304903146@qq.com>
2022-03-18 15:53:10 +08:00
Jiangjie.Bai
9ac3c6b120 fix: 修复去除rolebiding change 权限 2022-03-18 15:38:15 +08:00
19 changed files with 137 additions and 43 deletions

View File

@@ -8,6 +8,7 @@ from django.conf import settings
from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin
from common.tree import TreeNode
from common.utils import is_uuid
from assets.models import Asset, SystemUser
from ..utils import KubernetesTree
@@ -100,6 +101,9 @@ class ApplicationTreeNodeMixin:
type_category_mapper = const.AppType.type_category_mapper()
types = const.AppType.type_category_mapper().keys()
for tp in types:
# TODO: Temporary exclude mongodb
if tp == const.AppType.mongodb:
continue
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
continue
category = type_category_mapper.get(tp)
@@ -264,12 +268,12 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
'parameters': parameters
}
def get_remote_app_asset(self):
def get_remote_app_asset(self, raise_exception=True):
asset_id = self.attrs.get('asset')
if not asset_id:
if is_uuid(asset_id):
return Asset.objects.filter(id=asset_id).first()
if raise_exception:
raise ValueError("Remote App not has asset attr")
asset = Asset.objects.filter(id=asset_id).first()
return asset
class ApplicationUser(SystemUser):

View File

@@ -16,7 +16,7 @@ from perms.filters import AssetPermissionFilter
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from assets.api import FilterAssetByNodeMixin
from ..models import Asset, Node, Platform
from ..models import Asset, Node, Platform, Gateway
from .. import serializers
from ..tasks import (
update_assets_hardware_info_manual, test_assets_connectivity_manual,
@@ -181,7 +181,7 @@ class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
def check_permissions(self, request):
action = request.data.get('action')
action_perm_require = {
'refresh': 'assets.refresh_assethardwareinfo1',
'refresh': 'assets.refresh_assethardwareinfo',
}
perm_required = action_perm_require.get(action)
has = self.request.user.has_perm(perm_required)
@@ -199,7 +199,7 @@ class AssetGatewayListApi(generics.ListAPIView):
asset_id = self.kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
if not asset.domain:
return []
return Gateway.objects.none()
queryset = asset.domain.gateways.filter(protocol='ssh')
return queryset

View File

@@ -133,6 +133,14 @@ class AuthMixin:
self.password = password
def load_app_more_auth(self, app_id=None, username=None, user_id=None):
from applications.models import Application
app = get_object_or_none(Application, pk=app_id)
if app and app.category_remote_app:
# Remote app
self._load_remoteapp_more_auth(app, username, user_id)
return
# Other app
self._clean_auth_info_if_manual_login_mode()
# 加载临时认证信息
if self.login_mode == self.LOGIN_MANUAL:
@@ -148,6 +156,11 @@ class AuthMixin:
_username = username
self.username = _username
def _load_remoteapp_more_auth(self, app, username, user_id):
asset = app.get_remote_app_asset(raise_exception=False)
if asset:
self.load_asset_more_auth(asset_id=asset.id, username=username, user_id=user_id)
def load_asset_special_auth(self, asset, username=''):
"""
AuthBook 的数据状态

View File

@@ -7,7 +7,7 @@ from celery import shared_task
from ops.celery.decorator import (
register_as_period_task
)
from .models import UserLoginLog, OperateLog
from .models import UserLoginLog, OperateLog, FTPLog
from common.utils import get_log_keep_day
@@ -29,7 +29,7 @@ def clean_ftp_log_period():
now = timezone.now()
days = get_log_keep_day('FTP_LOG_KEEP_DAYS')
expired_day = now - datetime.timedelta(days=days)
OperateLog.objects.filter(datetime__lt=expired_day).delete()
FTPLog.objects.filter(datetime__lt=expired_day).delete()
@register_as_period_task(interval=3600*24)

View File

@@ -8,7 +8,6 @@ from .. import serializers
class AccessKeyViewSet(ModelViewSet):
permission_classes = (IsValidUser,)
serializer_class = serializers.AccessKeySerializer
search_fields = ['^id', '^secret']

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:449810c3661c09f6448b9c67e7a193f303a3bef7ccc3d0f1efe6e099804e782a
oid sha256:b1c6c0f9212f9d154a432d93785677ebc206eed4fd4338d3fe11b4b528d65c11
size 104323

View File

@@ -2050,7 +2050,7 @@ msgstr "请修改密码"
#: authentication/models.py:33 terminal/serializers/storage.py:30
msgid "Access key"
msgstr "Api key"
msgstr "API key"
#: authentication/models.py:40
msgid "Private Token"

View File

@@ -59,7 +59,7 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet):
raise ValidationError({"hosts": msg})
def check_permissions(self, request):
if not settings.SECURITY_COMMAND_EXECUTION and request.user.is_common_user:
if not settings.SECURITY_COMMAND_EXECUTION:
return self.permission_denied(request, "Command execution disabled")
return super().check_permissions(request)

View File

@@ -1,33 +1,54 @@
from functools import wraps
from django.db.models.signals import post_save, pre_delete, pre_save, post_delete
from django.dispatch import receiver
from orgs.models import Organization
from assets.models import Node
from perms.models import (AssetPermission, ApplicationPermission)
from perms.models import AssetPermission, ApplicationPermission
from users.models import UserGroup, User
from users.signals import pre_user_leave_org
from applications.models import Application
from terminal.models import Session
from rbac.models import OrgRoleBinding
from assets.models import Asset, SystemUser, Domain, Gateway
from orgs.caches import OrgResourceStatisticsCache
from common.utils import get_logger
logger = get_logger(__name__)
def refresh_user_amount_on_user_create_or_delete(user_id):
orgs = Organization.objects.filter(m2m_org_members__user_id=user_id).distinct()
def refresh_cache(name, org):
names = None
if isinstance(name, (str,)):
names = [name, ]
if isinstance(names, (list, tuple)):
for name in names:
OrgResourceStatisticsCache(org).expire(name)
OrgResourceStatisticsCache(Organization.root()).expire(name)
else:
logger.warning('refresh cache fail: {}'.format(name))
def refresh_user_amount_cache(user):
orgs = user.orgs.distinct()
for org in orgs:
org_cache = OrgResourceStatisticsCache(org)
org_cache.expire('users_amount')
OrgResourceStatisticsCache(Organization.root()).expire('users_amount')
refresh_cache('users_amount', org)
@receiver(post_save, sender=User)
def on_user_create_refresh_cache(sender, instance, created, **kwargs):
@receiver(post_save, sender=OrgRoleBinding)
def on_user_create_or_invite_refresh_cache(sender, instance, created, **kwargs):
if created:
refresh_user_amount_on_user_create_or_delete(instance.id)
refresh_cache('users_amount', instance.org)
@receiver(pre_user_leave_org)
def on_user_remove_refresh_cache(sender, org=None, **kwargs):
refresh_cache('users_amount', org)
@receiver(pre_delete, sender=User)
def on_user_delete_refresh_cache(sender, instance, **kwargs):
refresh_user_amount_on_user_create_or_delete(instance.id)
refresh_user_amount_cache(instance)
# @receiver(m2m_changed, sender=OrganizationMember)

View File

@@ -16,7 +16,7 @@ class RBACBackend(JMSBaseAuthBackend):
return False
def has_perm(self, user_obj, perm, obj=None):
if not user_obj.is_active:
if not user_obj.is_active or not perm:
raise PermissionDenied()
if perm == '*':
return True

View File

@@ -22,7 +22,6 @@ exclude_permissions = (
('common', 'setting', '*', '*'),
('authentication', 'privatetoken', '*', '*'),
('authentication', 'accesskey', 'change,delete', 'accesskey'),
('authentication', 'connectiontoken', 'change,delete', 'connectiontoken'),
('authentication', 'ssotoken', '*', '*'),
('authentication', 'superconnectiontoken', 'change,delete', 'superconnectiontoken'),
@@ -49,6 +48,8 @@ exclude_permissions = (
('rbac', 'contenttype', '*', '*'),
('rbac', 'permission', 'add,delete,change', 'permission'),
('rbac', 'rolebinding', '*', '*'),
('rbac', 'systemrolebinding', 'change', 'systemrolebinding'),
('rbac', 'orgrolebinding', 'change', 'orgrolebinding'),
('rbac', 'role', '*', '*'),
('ops', 'adhoc', 'delete,change', '*'),
('ops', 'adhocexecution', 'add,delete,change', '*'),
@@ -99,6 +100,7 @@ only_system_permissions = (
('orgs', 'organization', '*', '*'),
('xpack', 'license', '*', '*'),
('settings', 'setting', '*', '*'),
('tickets', '*', '*', '*'),
('ops', 'task', 'view', 'taskmonitor'),
('terminal', 'terminal', '*', '*'),
('terminal', 'commandstorage', '*', '*'),
@@ -106,6 +108,7 @@ only_system_permissions = (
('terminal', 'status', '*', '*'),
('terminal', 'task', '*', '*'),
('authentication', '*', '*', '*'),
('tickets', '*', '*', '*'),
)
only_org_permissions = (

View File

@@ -93,7 +93,7 @@ class RBACPermission(permissions.DjangoModelPermissions):
try:
queryset = self._queryset(view)
model_cls = queryset.model
except AssertionError:
except:
model_cls = None
return model_cls

View File

@@ -87,7 +87,6 @@ special_pid_mapper = {
'terminal.status': 'terminal_node',
'terminal.task': 'terminal_node',
'audits.ftplog': 'terminal',
'rbac.menupermission': 'view_other',
'perms.view_myassets': 'my_assets',
'perms.view_myapps': 'my_apps',
'ops.add_commandexecution': 'view_workspace',

View File

@@ -6,7 +6,7 @@ from common.exceptions import JMSException
from common.utils import lazyproperty
from rbac.permissions import RBACPermission
from tickets import serializers
from tickets.models import Ticket
from tickets.models import Ticket, Comment
from tickets.permissions.comment import IsAssignee, IsApplicant, IsSwagger
@@ -36,5 +36,7 @@ class CommentViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet):
return context
def get_queryset(self):
if getattr(self, 'swagger_fake_view', False):
return Comment.objects.none()
queryset = self.ticket.comments.all()
return queryset

View File

@@ -108,6 +108,9 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, SuggestionMixin, BulkModelV
self.check_object_permissions(self.request, user)
return super().perform_bulk_update(serializer)
def allow_bulk_destroy(self, qs, filtered):
return filtered.count() < qs.count()
def perform_bulk_destroy(self, objects):
for obj in objects:
self.check_object_permissions(self.request, obj)

View File

@@ -22,6 +22,7 @@ from orgs.models import Organization
from common.utils import date_expired_default, get_logger, lazyproperty, random_string
from common import fields
from django.db.models import TextChoices
from rbac.const import Scope
from ..signals import post_user_change_password, post_user_leave_org, pre_user_leave_org
__all__ = ['User', 'UserPasswordHistory']
@@ -204,6 +205,13 @@ class RoleManager(models.Manager):
return
return self.role_bindings.delete()
def refresh_user_amount_cache(self):
from orgs.signal_handlers.cache import refresh_user_amount_cache, refresh_cache
if current_org.is_root():
refresh_cache('users_amount', current_org)
elif self.scope == Scope.org:
refresh_user_amount_cache(self.user)
def add(self, *roles):
from rbac.models import RoleBinding
items = []
@@ -224,6 +232,9 @@ class RoleManager(models.Manager):
RoleBinding.objects.bulk_create(items, ignore_conflicts=True)
except Exception as e:
logger.error('Create role binding error: {}'.format(e))
finally:
self.user.expire_users_rbac_perms_cache()
self.refresh_user_amount_cache()
def set(self, roles):
self.clear()
@@ -237,16 +248,16 @@ class RoleManager(models.Manager):
class OrgRoleManager(RoleManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
from rbac.const import Scope
self.scope = Scope.org
super().__init__(*args, **kwargs)
class SystemRoleManager(RoleManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
from rbac.const import Scope
self.scope = Scope.system
super().__init__(*args, **kwargs)
class RoleMixin:
@@ -257,6 +268,8 @@ class RoleMixin:
_org_roles = None
_system_roles = None
PERM_CACHE_KEY = 'USER_PERMS_{}_{}'
_is_superuser = None
_update_superuser = False
@lazyproperty
def roles(self):
@@ -288,17 +301,25 @@ class RoleMixin:
key = cls.PERM_CACHE_KEY.format('*', '*')
cache.delete_pattern(key)
@lazyproperty
@property
def is_superuser(self):
"""
由于这里用了 cache ,所以不能改成 self.system_roles.filter().exists() 会查询的
"""
if self._is_superuser is not None:
return self._is_superuser
from rbac.builtin import BuiltinRole
# return self.system_roles.all().filter(id=BuiltinRole.system_admin.id).exists()
ids = [str(r.id) for r in self.system_roles.all()]
yes = BuiltinRole.system_admin.id in ids
self._is_superuser = yes
return yes
@is_superuser.setter
def is_superuser(self, value):
self._is_superuser = value
self._update_superuser = True
@lazyproperty
def is_org_admin(self):
from rbac.builtin import BuiltinRole

View File

@@ -49,6 +49,8 @@ class RolesSerializerMixin(serializers.Serializer):
return fields
action = view.action or 'list'
if action in ('partial_bulk_update', 'bulk_update', 'partial_update', 'update'):
action = 'create'
model_cls_field_mapper = {
SystemRoleBinding: ['system_roles', 'system_roles_display'],
OrgRoleBinding: ['org_roles', 'system_roles_display']
@@ -130,6 +132,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
'date_joined', 'last_login', 'created_by', 'is_first_login',
'wecom_id', 'dingtalk_id', 'feishu_id'
]
disallow_self_update_fields = ['is_active']
extra_kwargs = {
'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True},
'public_key': {'write_only': True},
@@ -178,7 +181,23 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
attrs.pop(field, None)
return attrs
def check_disallow_self_update_fields(self, attrs):
request = self.context.get('request')
if not request or not request.user.is_authenticated:
return attrs
if not self.instance:
return attrs
if request.user.id != self.instance.id:
return attrs
disallow_fields = set(list(attrs.keys())) & set(self.Meta.disallow_self_update_fields)
if not disallow_fields:
return attrs
# 用户自己不能更新自己的一些字段
error = 'User Cannot self-update fields: {}'.format(disallow_fields)
raise serializers.ValidationError(error)
def validate(self, attrs):
attrs = self.check_disallow_self_update_fields(attrs)
attrs = self.change_password_to_raw(attrs)
attrs = self.clean_auth_fields(attrs)
attrs.pop('password_strategy', None)
@@ -203,17 +222,6 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
field.set(value)
return instance
def validate_is_active(self, is_active):
request = self.context.get('request')
if not request or not request.user.is_authenticated:
return is_active
user = request.user
if user.id == self.instance.id and not is_active:
# 用户自己不能禁用启用自己
raise serializers.ValidationError("Cannot inactive self")
return is_active
def update(self, instance, validated_data):
save_handler = partial(super().update, instance)
instance = self.save_and_set_custom_m2m_fields(validated_data, save_handler, created=False)

View File

@@ -60,9 +60,30 @@ def save_passwd_change(sender, instance: User, **kwargs):
)
def update_role_superuser_if_need(user):
if not user._update_superuser:
return
value = user._is_superuser
from rbac.models import SystemRoleBinding
from rbac.builtin import BuiltinRole
role = BuiltinRole.system_admin.get_role()
kwargs = {'user_id': user.id, 'role_id': role.id, 'scope': 'system'}
exists = SystemRoleBinding.objects.filter(**kwargs).exists()
# 需要添加并且不存在
if value and not exists:
SystemRoleBinding.objects.create(**kwargs)
# 需要删除并且存在
elif not value and exists:
SystemRoleBinding.objects.filter(**kwargs).delete()
else:
print("No need operate")
@receiver(post_save, sender=User)
@on_transaction_commit
def on_user_create_set_default_system_role(sender, instance, created, **kwargs):
update_role_superuser_if_need(instance)
if not created:
return
has_system_role = instance.system_roles.all().exists()

View File

@@ -58,7 +58,7 @@ def clean_db_content_types():
('perms', 'applicationpermission', 'view_permuserapplication'),
('perms', 'assetpermission', 'view_permuserasset'),
('perms', 'assetpermission', 'view_permusergroupasset'),
('rbac', 'menupermission', 'view_dashboard'),
('applications', 'databaseapp', 'add_databaseapp'),
('applications', 'databaseapp', 'change_databaseapp'),
('applications', 'databaseapp', 'delete_databaseapp'),