jumpserver/apps/perms/models/asset_permission.py

279 lines
9.3 KiB
Python

import uuid
import logging
from functools import reduce
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.db.models import F, Q, TextChoices
from collections import defaultdict
from assets.models import Asset, Node, FamilyMixin, Account
from orgs.mixins.models import OrgModelMixin
from orgs.mixins.models import OrgManager
from common.utils import lazyproperty, date_expired_default
from common.db.models import BaseCreateUpdateModel, UnionQuerySet
from .const import Action, SpecialAccount
__all__ = [
'AssetPermission', 'PermNode',
'UserAssetGrantedTreeNodeRelation',
'Action'
]
# 使用场景
logger = logging.getLogger(__name__)
class AssetPermissionQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
def valid(self):
return self.active().filter(date_start__lt=timezone.now()) \
.filter(date_expired__gt=timezone.now())
def inactive(self):
return self.filter(is_active=False)
def invalid(self):
now = timezone.now()
q = (Q(is_active=False) | Q(date_start__gt=now) | Q(date_expired__lt=now))
return self.filter(q)
def filter_by_accounts(self, accounts):
q = Q(accounts__contains=list(accounts)) | \
Q(accounts__contains=SpecialAccount.ALL.value)
return self.filter(q)
class AssetPermissionManager(OrgManager):
def valid(self):
return self.get_queryset().valid()
class AssetPermission(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
users = models.ManyToManyField(
'users.User', related_name='%(class)ss', blank=True, verbose_name=_("User")
)
user_groups = models.ManyToManyField(
'users.UserGroup', related_name='%(class)ss', blank=True, verbose_name=_("User group")
)
assets = models.ManyToManyField(
'assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")
)
nodes = models.ManyToManyField(
'assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")
)
# 特殊的账号: @ALL, @INPUT @USER 默认包含,将来在全局设置中进行控制.
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
actions = models.IntegerField(
choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_("Actions")
)
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_start = models.DateTimeField(
default=timezone.now, db_index=True, verbose_name=_("Date start")
)
date_expired = models.DateTimeField(
default=date_expired_default, db_index=True, verbose_name=_('Date expired')
)
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket'))
comment = models.TextField(verbose_name=_('Comment'), blank=True)
objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)()
class Meta:
unique_together = [('org_id', 'name')]
verbose_name = _("Asset permission")
ordering = ('name',)
permissions = []
def __str__(self):
return self.name
@property
def is_expired(self):
if self.date_expired > timezone.now() > self.date_start:
return False
return True
@property
def is_valid(self):
if not self.is_expired and self.is_active:
return True
return False
def get_all_users(self):
from users.models import User
user_ids = self.users.all().values_list('id', flat=True)
group_ids = self.user_groups.all().values_list('id', flat=True)
user_ids = list(user_ids)
group_ids = list(group_ids)
qs1 = User.objects.filter(id__in=user_ids).distinct()
qs2 = User.objects.filter(groups__id__in=group_ids).distinct()
qs = UnionQuerySet(qs1, qs2)
return qs
def get_all_assets(self, flat=False):
from assets.models import Node
nodes_keys = self.nodes.all().values_list('key', flat=True)
asset_ids = set(self.assets.all().values_list('id', flat=True))
nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys)
asset_ids.update(nodes_asset_ids)
if flat:
return asset_ids
assets = Asset.objects.filter(id__in=asset_ids)
return assets
def get_all_accounts(self, flat=False):
"""
:return: 返回授权的所有账号对象 Account
"""
asset_ids = self.get_all_assets(flat=True)
q = Q(asset_id__in=asset_ids)
if not self.is_perm_all_accounts:
q &= Q(username__in=self.accounts)
accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username')
if not flat:
return accounts
return accounts.values_list('id', flat=True)
@property
def is_perm_all_accounts(self):
return SpecialAccount.ALL in self.accounts
@lazyproperty
def users_amount(self):
return self.users.count()
@lazyproperty
def user_groups_amount(self):
return self.user_groups.count()
@lazyproperty
def assets_amount(self):
return self.assets.count()
@lazyproperty
def nodes_amount(self):
return self.nodes.count()
def users_display(self):
names = [user.username for user in self.users.all()]
return names
def user_groups_display(self):
names = [group.name for group in self.user_groups.all()]
return names
def assets_display(self):
names = [asset.name for asset in self.assets.all()]
return names
def nodes_display(self):
names = [node.full_value for node in self.nodes.all()]
return names
class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpdateModel):
class NodeFrom(TextChoices):
granted = 'granted', 'Direct node granted'
child = 'child', 'Have children node'
asset = 'asset', 'Direct asset granted'
user = models.ForeignKey('users.User', db_constraint=False, on_delete=models.CASCADE)
node = models.ForeignKey('assets.Node', default=None, on_delete=models.CASCADE,
db_constraint=False, null=False, related_name='granted_node_rels')
node_key = models.CharField(max_length=64, verbose_name=_("Key"), db_index=True)
node_parent_key = models.CharField(max_length=64, default='', verbose_name=_('Parent key'),
db_index=True)
node_from = models.CharField(choices=NodeFrom.choices, max_length=16, db_index=True)
node_assets_amount = models.IntegerField(default=0)
@property
def key(self):
return self.node_key
@property
def parent_key(self):
return self.node_parent_key
@classmethod
def get_node_granted_status(cls, user, key):
ancestor_keys = set(cls.get_node_ancestor_keys(key, with_self=True))
ancestor_rel_nodes = cls.objects.filter(user=user, node_key__in=ancestor_keys)
for rel_node in ancestor_rel_nodes:
if rel_node.key == key:
return rel_node.node_from, rel_node
if rel_node.node_from == cls.NodeFrom.granted:
return cls.NodeFrom.granted, None
return '', None
class PermNode(Node):
class Meta:
proxy = True
ordering = []
# 特殊节点
UNGROUPED_NODE_KEY = 'ungrouped'
UNGROUPED_NODE_VALUE = _('Ungrouped')
FAVORITE_NODE_KEY = 'favorite'
FAVORITE_NODE_VALUE = _('Favorite')
node_from = ''
granted_assets_amount = 0
annotate_granted_node_rel_fields = {
'granted_assets_amount': F('granted_node_rels__node_assets_amount'),
'node_from': F('granted_node_rels__node_from')
}
def use_granted_assets_amount(self):
self.assets_amount = self.granted_assets_amount
@classmethod
def get_ungrouped_node(cls, assets_amount):
return cls(
id=cls.UNGROUPED_NODE_KEY,
key=cls.UNGROUPED_NODE_KEY,
value=cls.UNGROUPED_NODE_VALUE,
assets_amount=assets_amount
)
@classmethod
def get_favorite_node(cls, assets_amount):
node = cls(
id=cls.FAVORITE_NODE_KEY,
key=cls.FAVORITE_NODE_KEY,
value=cls.FAVORITE_NODE_VALUE,
)
node.assets_amount = assets_amount
return node
def get_granted_status(self, user):
status, rel_node = UserAssetGrantedTreeNodeRelation.get_node_granted_status(user, self.key)
self.node_from = status
if rel_node:
self.granted_assets_amount = rel_node.node_assets_amount
return status
def save(self):
# 这是个只读 Model
raise NotImplementedError
class PermedAsset(Asset):
class Meta:
proxy = True
verbose_name = _('Permed asset')
permissions = [
('view_myassets', _('Can view my assets')),
('view_userassets', _('Can view user assets')),
('view_usergroupassets', _('Can view usergroup assets')),
]