fix: rbac 合并 (#7658)

* perf: 修复一些错误权限位

* Pr@fix rbac@fix rbac permissions (#7648)

* fix: 确保每次 migrate 执行更新 role permissions

* perf: 修改 choices

* feat: 兼容apple m1

* perf: 修改 migrations role permissions

* perf: pymysql 导入

* perf: admin 判断

* fix: 修复消息订阅权限

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: Aaron3S <chenyang@fit2cloud.com>
Co-authored-by: feng626 <1304903146@qq.com>
pull/7660/head
Jiangjie.Bai 2022-02-21 16:24:03 +08:00 committed by GitHub
parent 783c163324
commit 83ff8dbf26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 59 additions and 46 deletions

View File

@ -54,6 +54,6 @@ class ApplicationAccountSecretViewSet(ApplicationAccountViewSet):
permission_classes = [RBACPermission, NeedMFAVerify] permission_classes = [RBACPermission, NeedMFAVerify]
http_method_names = ['get', 'options'] http_method_names = ['get', 'options']
rbac_perms = { rbac_perms = {
'retrieve': 'view_applicationaccountsecret', 'retrieve': 'applications.view_applicationaccountsecret',
'list': 'view_applicationaccountsecret', 'list': 'applications.view_applicationaccountsecret',
} }

View File

@ -1,10 +1,10 @@
# coding: utf-8 # coding: utf-8
# #
from django.db.models import TextChoices from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
class AppCategory(TextChoices): class AppCategory(models.TextChoices):
db = 'db', _('Database') db = 'db', _('Database')
remote_app = 'remote_app', _('Remote app') remote_app = 'remote_app', _('Remote app')
cloud = 'cloud', 'Cloud' cloud = 'cloud', 'Cloud'
@ -14,7 +14,7 @@ class AppCategory(TextChoices):
return dict(cls.choices).get(category, '') return dict(cls.choices).get(category, '')
class AppType(TextChoices): class AppType(models.TextChoices):
# db category # db category
mysql = 'mysql', 'MySQL' mysql = 'mysql', 'MySQL'
redis = 'redis', 'Redis' redis = 'redis', 'Redis'

View File

@ -8,7 +8,6 @@ from functools import reduce
from collections import OrderedDict from collections import OrderedDict
from django.db import models from django.db import models
from common.db.models import TextChoices
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
@ -59,7 +58,7 @@ class AssetQuerySet(models.QuerySet):
class ProtocolsMixin: class ProtocolsMixin:
protocols = '' protocols = ''
class Protocol(TextChoices): class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH' ssh = 'ssh', 'SSH'
rdp = 'rdp', 'RDP' rdp = 'rdp', 'RDP'
telnet = 'telnet', 'Telnet' telnet = 'telnet', 'Telnet'

View File

@ -7,7 +7,6 @@ import random
from django.core.cache import cache from django.core.cache import cache
import paramiko import paramiko
from django.db import models from django.db import models
from django.db.models import TextChoices
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
@ -55,7 +54,7 @@ class Gateway(BaseUser):
UNCONNECTIVE_SILENCE_PERIOD_KEY_TMPL = 'asset_unconnective_gateway_silence_period_{}' UNCONNECTIVE_SILENCE_PERIOD_KEY_TMPL = 'asset_unconnective_gateway_silence_period_{}'
UNCONNECTIVE_SILENCE_PERIOD_BEGIN_VALUE = 60 * 5 UNCONNECTIVE_SILENCE_PERIOD_BEGIN_VALUE = 60 * 5
class Protocol(TextChoices): class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH' ssh = 'ssh', 'SSH'
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)

View File

@ -5,13 +5,11 @@
import logging import logging
from django.db import models from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.cache import cache from django.core.cache import cache
from common.utils import signer, get_object_or_none from common.utils import signer, get_object_or_none
from common.db.models import TextChoices
from .base import BaseUser from .base import BaseUser
from .asset import Asset from .asset import Asset
from .authbook import AuthBook from .authbook import AuthBook
@ -24,7 +22,7 @@ logger = logging.getLogger(__name__)
class ProtocolMixin: class ProtocolMixin:
protocol: str protocol: str
class Protocol(TextChoices): class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH' ssh = 'ssh', 'SSH'
rdp = 'rdp', 'RDP' rdp = 'rdp', 'RDP'
telnet = 'telnet', 'Telnet' telnet = 'telnet', 'Telnet'
@ -217,7 +215,7 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser):
(LOGIN_MANUAL, _('Manually input')) (LOGIN_MANUAL, _('Manually input'))
) )
class Type(TextChoices): class Type(models.TextChoices):
common = 'common', _('Common user') common = 'common', _('Common user')
admin = 'admin', _('Admin user') admin = 'admin', _('Admin user')

View File

@ -1,4 +1,10 @@
import os import os
import platform
if platform.system() == 'Darwin' and platform.machine() == 'arm64':
import pymysql
pymysql.version_info = (1, 4, 2, "final", 0)
pymysql.install_as_MySQLdb()
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -68,10 +74,9 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.forms', 'django.forms',
'simple_history', 'simple_history', # 这个要放到最后,别特么瞎改顺序
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
@ -92,7 +97,6 @@ MIDDLEWARE = [
'simple_history.middleware.HistoryRequestMiddleware', 'simple_history.middleware.HistoryRequestMiddleware',
] ]
ROOT_URLCONF = 'jumpserver.urls' ROOT_URLCONF = 'jumpserver.urls'
TEMPLATES = [ TEMPLATES = [
@ -158,13 +162,14 @@ DATABASES = {
'OPTIONS': DB_OPTIONS 'OPTIONS': DB_OPTIONS
} }
} }
DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'certs', 'db_ca.pem') DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'certs', 'db_ca.pem')
if CONFIG.DB_ENGINE.lower() == 'mysql': if CONFIG.DB_ENGINE.lower() == 'mysql':
DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'" DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'"
if os.path.isfile(DB_CA_PATH): if os.path.isfile(DB_CA_PATH):
DB_OPTIONS['ssl'] = {'ca': DB_CA_PATH} DB_OPTIONS['ssl'] = {'ca': DB_CA_PATH}
# Password validation # Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
# #
@ -234,7 +239,6 @@ EMAIL_RECIPIENT = CONFIG.EMAIL_RECIPIENT
EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL
EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS
# Custom User Auth model # Custom User Auth model
AUTH_USER_MODEL = 'users.User' AUTH_USER_MODEL = 'users.User'

View File

@ -19,14 +19,15 @@ __all__ = (
class BackendListView(APIView): class BackendListView(APIView):
permission_classes = [IsValidUser]
def get(self, request): def get(self, request):
data = [ data = [
{ {
'name': backend, 'name': backend,
'name_display': backend.label 'name_display': backend.label
} }
for backend in BACKEND.choices for backend in BACKEND if backend.is_enable
if backend.is_enable
] ]
return Response(data=data) return Response(data=data)
@ -91,7 +92,6 @@ class UserMsgSubscriptionViewSet(ListModelMixin,
return queryset return queryset
def get_all_test_messages(request): def get_all_test_messages(request):
import textwrap import textwrap
from ..notifications import Message from ..notifications import Message
@ -128,5 +128,3 @@ def get_all_test_messages(request):
<hr /> <hr />
""").format(msg_cls.__name__, msg_text) """).format(msg_cls.__name__, msg_text)
return HttpResponse(html_data + text_data) return HttpResponse(html_data + text_data)

View File

@ -13,7 +13,7 @@ class CeleryTaskLogView(PermissionsMixin, TemplateView):
template_name = 'ops/celery_task_log.html' template_name = 'ops/celery_task_log.html'
permission_classes = [RBACPermission] permission_classes = [RBACPermission]
rbac_perms = { rbac_perms = {
'GET': 'view_tasklog' 'GET': 'ops.view_tasklog'
} }
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):

View File

@ -56,7 +56,7 @@ class MyGrantedApplicationSystemUsersApi(RoleUserMixin, GrantedApplicationSystem
@method_decorator(tmp_to_root_org(), name='get') @method_decorator(tmp_to_root_org(), name='get')
class ValidateUserApplicationPermissionApi(APIView): class ValidateUserApplicationPermissionApi(APIView):
rbac_perms = { rbac_perms = {
'GET': 'view_applicationpermission' 'GET': 'ops.view_applicationpermission'
} }
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):

View File

@ -123,7 +123,7 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
class UserGroupGrantedNodesApi(ListAPIView): class UserGroupGrantedNodesApi(ListAPIView):
serializer_class = serializers.NodeGrantedSerializer serializer_class = serializers.NodeGrantedSerializer
rbac_perms = { rbac_perms = {
'list': 'view_userassets' 'list': 'perms.view_userassets'
} }
def get_queryset(self): def get_queryset(self):
@ -137,7 +137,7 @@ class UserGroupGrantedNodesApi(ListAPIView):
class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIView): class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIView):
rbac_perms = { rbac_perms = {
'list': 'view_userassets' 'list': 'perms.view_userassets'
} }
def get_children_nodes(self, parent_key): def get_children_nodes(self, parent_key):

View File

@ -1,9 +1,8 @@
import logging import logging
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import F from django.db.models import F, TextChoices
from common.db.models import TextChoices
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from common.db import models from common.db import models
from common.utils import lazyproperty from common.utils import lazyproperty

View File

@ -5,3 +5,7 @@ from django.utils.translation import ugettext_lazy as _
class RBACConfig(AppConfig): class RBACConfig(AppConfig):
name = 'rbac' name = 'rbac'
verbose_name = _('RBAC') verbose_name = _('RBAC')
def ready(self):
from . import signal_handlers
super().ready()

View File

@ -64,11 +64,11 @@ class PredefineRole:
} }
return defaults return defaults
def get_or_create_role(self): def update_or_create_role(self):
from rbac.models import Role from rbac.models import Role
defaults = self._get_defaults() defaults = self._get_defaults()
permissions = defaults.pop('permissions', []) permissions = defaults.pop('permissions', [])
role, created = Role.objects.get_or_create(defaults, id=self.id) role, created = Role.objects.update_or_create(defaults, id=self.id)
role.permissions.set(permissions) role.permissions.set(permissions)
return role, created return role, created
@ -125,10 +125,10 @@ class BuiltinRole:
return mapper[name].get_role() return mapper[name].get_role()
@classmethod @classmethod
def sync_to_db(cls): def sync_to_db(cls, show_msg=False):
roles = cls.get_roles() roles = cls.get_roles()
for pre_role in roles.values(): for pre_role in roles.values():
role, created = pre_role.get_or_create_role() role, created = pre_role.update_or_create_role()
print("Create builtin Role: {} - {}".format(role.name, created)) if show_msg:
print("Update builtin Role: {} - {}".format(role.name, created))

View File

@ -5,7 +5,7 @@ from rbac.builtin import BuiltinRole
def create_builtin_roles(apps, schema_editor): def create_builtin_roles(apps, schema_editor):
BuiltinRole.sync_to_db() BuiltinRole.sync_to_db(show_msg=True)
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,7 +1,5 @@
import uuid
from typing import Callable from typing import Callable
from django.db import models
from django.db.models import F, Count, Q from django.db.models import F, Count, Q
from django.apps import apps from django.apps import apps
from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.translation import ugettext_lazy as _, ugettext

View File

@ -49,10 +49,10 @@ class Role(JMSModel):
return '%s(%s)' % (self.name, self.get_scope_display()) return '%s(%s)' % (self.name, self.get_scope_display())
def is_system_admin(self): def is_system_admin(self):
return self.id == self.BuiltinRole.system_admin.id and self.builtin return str(self.id) == self.BuiltinRole.system_admin.id and self.builtin
def is_org_admin(self): def is_org_admin(self):
return self.id == self.BuiltinRole.org_admin.id and self.builtin return str(self.id) == self.BuiltinRole.org_admin.id and self.builtin
def is_admin(self): def is_admin(self):
yes = self.is_system_admin() or self.is_org_admin() yes = self.is_system_admin() or self.is_org_admin()

View File

@ -0,0 +1,14 @@
from django.dispatch import receiver
from django.db.models.signals import post_migrate
from django.apps import apps
from .builtin import BuiltinRole
@receiver(post_migrate)
def after_migrate_update_builtin_role_permissions(sender, app_config, **kwargs):
# 最后一个 app migrations 后执行, 更新内置角色的权限
last_app = list(apps.get_app_configs())[-1]
if app_config.name == last_app.name:
print("After migration, update builtin role permissions")
BuiltinRole.sync_to_db()

View File

@ -85,7 +85,7 @@ class TerminalViewSet(JMSBulkModelViewSet):
class TerminalConfig(APIView): class TerminalConfig(APIView):
rbac_perms = { rbac_perms = {
'GET': 'view_terminalconfig' 'GET': 'terminal.view_terminalconfig'
} }
def get(self, request): def get(self, request):

View File

@ -13,7 +13,7 @@ from django.core.cache import cache
from assets.models import Asset from assets.models import Asset
from users.models import User from users.models import User
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from common.db.models import TextChoices from django.db.models import TextChoices
from ..backends import get_multi_command_storage from ..backends import get_multi_command_storage

View File

@ -21,7 +21,7 @@ from orgs.utils import current_org
from orgs.models import Organization from orgs.models import Organization
from common.utils import date_expired_default, get_logger, lazyproperty, random_string from common.utils import date_expired_default, get_logger, lazyproperty, random_string
from common import fields from common import fields
from common.db.models import TextChoices from django.db.models import TextChoices
from ..signals import post_user_change_password, post_user_leave_org, pre_user_leave_org from ..signals import post_user_change_password, post_user_leave_org, pre_user_leave_org
__all__ = ['User', 'UserPasswordHistory'] __all__ = ['User', 'UserPasswordHistory']

View File

@ -12,7 +12,7 @@ chardet==3.0.4
configparser==3.5.0 configparser==3.5.0
coreapi==2.3.3 coreapi==2.3.3
coreschema==0.0.4 coreschema==0.0.4
cryptography==3.3.2 cryptography==36.0.1
decorator==4.1.2 decorator==4.1.2
Django==3.1.13 Django==3.1.13
django-auth-ldap==2.2.0 django-auth-ldap==2.2.0
@ -52,8 +52,8 @@ passlib==1.7.1
Pillow==8.3.2 Pillow==8.3.2
pyasn1==0.4.8 pyasn1==0.4.8
pycparser==2.19 pycparser==2.19
pycryptodome==3.10.1 pycryptodome==3.12.0
pycryptodomex==3.10.1 pycryptodomex==3.12.0
pyotp==2.2.6 pyotp==2.2.6
PyNaCl==1.2.1 PyNaCl==1.2.1
python-dateutil==2.8.2 python-dateutil==2.8.2