mirror of https://github.com/jumpserver/jumpserver
perf: 优化 acl
parent
632627db11
commit
338ab5c634
|
@ -0,0 +1,44 @@
|
||||||
|
# Generated by Django 3.2.17 on 2023-04-25 09:04
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0010_alter_commandfilteracl_command_groups'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='new_accounts',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Account', verbose_name='Accounts'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='new_assets',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Asset', verbose_name='Assets'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='new_users',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='new_accounts',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Account', verbose_name='Accounts'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='new_assets',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Asset', verbose_name='Assets'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='new_users',
|
||||||
|
field=common.db.fields.JSONManyToManyField(default=dict, to='users.User', verbose_name='Users'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Generated by Django 3.2.17 on 2023-04-26 03:11
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_base_acl_users_assets_accounts(apps, *args):
|
||||||
|
cmd_acl_model = apps.get_model('acls', 'CommandFilterACL')
|
||||||
|
login_asset_acl_model = apps.get_model('acls', 'LoginAssetACL')
|
||||||
|
|
||||||
|
for model in [cmd_acl_model, login_asset_acl_model]:
|
||||||
|
for obj in model.objects.all():
|
||||||
|
user_names = (obj.users or {}).get('username_group', [])
|
||||||
|
obj.new_users = {
|
||||||
|
"type": "attrs",
|
||||||
|
"attrs": [{"name": "username", "value": user_names, "match": "in"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
asset_names = (obj.assets or {}).get('name_group', [])
|
||||||
|
asset_attrs = []
|
||||||
|
if asset_names:
|
||||||
|
asset_attrs.append({"name": "name", "value": asset_names, "match": "in"})
|
||||||
|
asset_address = (obj.assets or {}).get('address_group', [])
|
||||||
|
if asset_address:
|
||||||
|
asset_attrs.append({"name": "address", "value": asset_address, "match": "ip_in", "rel": "or"})
|
||||||
|
obj.new_assets = {"type": "attrs", "attrs": asset_attrs}
|
||||||
|
|
||||||
|
account_usernames = (obj.accounts or {}).get('username_group', [])
|
||||||
|
obj.new_accounts = {
|
||||||
|
"type": "attrs",
|
||||||
|
"attrs": [{"name": "username", "value": account_usernames, "match": "in"}]
|
||||||
|
}
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0011_auto_20230425_1704'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_base_acl_users_assets_accounts)
|
||||||
|
]
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Generated by Django 3.2.17 on 2023-04-26 09:59
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('acls', '0012_auto_20230426_1111'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='accounts',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='assets',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
name='users',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='accounts',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='assets',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
name='users',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
old_name='new_accounts',
|
||||||
|
new_name='accounts',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
old_name='new_assets',
|
||||||
|
new_name='assets',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='commandfilteracl',
|
||||||
|
old_name='new_users',
|
||||||
|
new_name='users',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
old_name='new_accounts',
|
||||||
|
new_name='accounts',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
old_name='new_assets',
|
||||||
|
new_name='assets',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='loginassetacl',
|
||||||
|
old_name='new_users',
|
||||||
|
new_name='users',
|
||||||
|
),
|
||||||
|
]
|
|
@ -3,6 +3,7 @@ from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from common.db.fields import JSONManyToManyField
|
||||||
from common.db.models import JMSBaseModel
|
from common.db.models import JMSBaseModel
|
||||||
from common.utils import contains_ip
|
from common.utils import contains_ip
|
||||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||||
|
@ -95,11 +96,11 @@ class BaseACL(JMSBaseModel):
|
||||||
|
|
||||||
class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
|
class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
|
||||||
# username_group
|
# username_group
|
||||||
users = models.JSONField(verbose_name=_('User'))
|
users = JSONManyToManyField('users.User', default=dict, verbose_name=_('Users'))
|
||||||
# name_group, address_group
|
# name_group, address_group
|
||||||
assets = models.JSONField(verbose_name=_('Asset'))
|
assets = JSONManyToManyField('assets.Asset', default=dict, verbose_name=_('Assets'))
|
||||||
# username_group
|
# username_group
|
||||||
accounts = models.JSONField(verbose_name=_('Account'))
|
accounts = JSONManyToManyField('assets.Account', default=dict, verbose_name=_('Accounts'))
|
||||||
|
|
||||||
objects = OrgACLManager.from_queryset(UserAssetAccountACLQuerySet)()
|
objects = OrgACLManager.from_queryset(UserAssetAccountACLQuerySet)()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
@ -291,6 +292,34 @@ class RelatedManager:
|
||||||
self.value = value
|
self.value = value
|
||||||
self.instance.__dict__[self.field.name] = value
|
self.instance.__dict__[self.field.name] = value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ip_in_q(name, val):
|
||||||
|
q = Q()
|
||||||
|
if isinstance(val, str):
|
||||||
|
val = [val]
|
||||||
|
for ip in val:
|
||||||
|
if not ip:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if ip == '*':
|
||||||
|
return Q()
|
||||||
|
elif '/' in ip:
|
||||||
|
network = ipaddress.ip_network(ip)
|
||||||
|
ips = network.hosts()
|
||||||
|
q |= Q(**{"{}__in".format(name): ips})
|
||||||
|
elif '-' in ip:
|
||||||
|
start_ip, end_ip = ip.split('-')
|
||||||
|
start_ip = ipaddress.ip_address(start_ip)
|
||||||
|
end_ip = ipaddress.ip_address(end_ip)
|
||||||
|
q |= Q(**{"{}__range".format(name): (start_ip, end_ip)})
|
||||||
|
elif len(ip.split('.')) == 4:
|
||||||
|
q |= Q(**{"{}__exact".format(name): ip})
|
||||||
|
else:
|
||||||
|
q |= Q(**{"{}__startswith".format(name): ip})
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return q
|
||||||
|
|
||||||
def _get_queryset(self):
|
def _get_queryset(self):
|
||||||
model = apps.get_model(self.field.to)
|
model = apps.get_model(self.field.to)
|
||||||
value = self.value
|
value = self.value
|
||||||
|
@ -303,20 +332,43 @@ class RelatedManager:
|
||||||
return model.objects.filter(id__in=value["ids"])
|
return model.objects.filter(id__in=value["ids"])
|
||||||
elif value["type"] == "attrs" and isinstance(value.get("attrs"), list):
|
elif value["type"] == "attrs" and isinstance(value.get("attrs"), list):
|
||||||
filters = Q()
|
filters = Q()
|
||||||
|
excludes = Q()
|
||||||
for attr in value["attrs"]:
|
for attr in value["attrs"]:
|
||||||
if not isinstance(attr, dict):
|
if not isinstance(attr, dict):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
name = attr.get('name')
|
name = attr.get('name')
|
||||||
val = attr.get('value')
|
val = attr.get('value')
|
||||||
match = attr.get('match', 'exact')
|
match = attr.get('match', 'exact')
|
||||||
|
rel = attr.get('rel', 'and')
|
||||||
if name is None or val is None:
|
if name is None or val is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
lookup = name
|
if val == '*':
|
||||||
if match in ("exact", "contains", "startswith", "endswith", "regex"):
|
filters = Q()
|
||||||
|
break
|
||||||
|
|
||||||
|
if match == 'ip_in':
|
||||||
|
q = self.get_ip_in_q(name, val)
|
||||||
|
elif match in ("exact", "contains", "startswith", "endswith", "regex"):
|
||||||
lookup = "{}__{}".format(name, match)
|
lookup = "{}__{}".format(name, match)
|
||||||
filters &= Q(**{lookup: val})
|
q = Q(**{lookup: val})
|
||||||
return model.objects.filter(filters)
|
elif match == "in" and isinstance(val, list):
|
||||||
|
if '*' not in val:
|
||||||
|
lookup = "{}__in".format(name)
|
||||||
|
q = Q(**{lookup: val})
|
||||||
|
else:
|
||||||
|
q = Q()
|
||||||
|
else:
|
||||||
|
q = Q(**{name: val})
|
||||||
|
|
||||||
|
if rel == 'or':
|
||||||
|
filters |= q
|
||||||
|
elif rel == 'not':
|
||||||
|
excludes |= q
|
||||||
|
else:
|
||||||
|
filters &= q
|
||||||
|
return model.objects.filter(filters).exclude(excludes)
|
||||||
else:
|
else:
|
||||||
return model.objects.none()
|
return model.objects.none()
|
||||||
|
|
||||||
|
@ -401,19 +453,16 @@ class JSONManyToManyField(models.JSONField):
|
||||||
if 'name' not in attr or 'value' not in attr:
|
if 'name' not in attr or 'value' not in attr:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def get_db_prep_value(self, manager, connection, prepared=False):
|
def get_db_prep_value(self, value, connection, prepared=False):
|
||||||
if manager is None:
|
return self.get_prep_value(value)
|
||||||
return None
|
|
||||||
v = manager.value
|
|
||||||
self._check_value(v)
|
|
||||||
return json.dumps(v)
|
|
||||||
|
|
||||||
def get_prep_value(self, manager):
|
def get_prep_value(self, value):
|
||||||
if manager is None:
|
if value is None:
|
||||||
return manager
|
return None
|
||||||
v = manager.value
|
if isinstance(value, RelatedManager):
|
||||||
self._check_value(v)
|
value = value.value
|
||||||
return json.dumps(v)
|
self._check_value(value)
|
||||||
|
return json.dumps(value)
|
||||||
|
|
||||||
def validate(self, value, model_instance):
|
def validate(self, value, model_instance):
|
||||||
super().validate(value, model_instance)
|
super().validate(value, model_instance)
|
||||||
|
|
Loading…
Reference in New Issue