mirror of https://github.com/jumpserver/jumpserver
Browse Source
* feat: 改密计划支持数据库改密 * fix: 将数据库账户信息不保存在资产信息里,保存到自己的存储中 * perf: 早餐村 * perf: 修改account * perf: 修改app和系统用户 * perf: 优化系统用户和应用关系 * fix: 修复oracle不可连接问题 Co-authored-by: ibuler <ibuler@qq.com> Co-authored-by: feng626 <1304903146@qq.com> Co-authored-by: Michael Bai <baijiangjie@gmail.com>pull/6787/head
fit2cloud-jiangweidong
3 years ago
committed by
GitHub
22 changed files with 655 additions and 523 deletions
@ -0,0 +1,76 @@
|
||||
# Generated by Django 3.1.12 on 2021-08-26 09:07 |
||||
|
||||
import assets.models.base |
||||
import common.fields.model |
||||
from django.conf import settings |
||||
import django.core.validators |
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
import simple_history.models |
||||
import uuid |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
('assets', '0076_delete_assetuser'), |
||||
('applications', '0009_applicationuser'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='HistoricalAccount', |
||||
fields=[ |
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), |
||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4)), |
||||
('name', models.CharField(max_length=128, verbose_name='Name')), |
||||
('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), |
||||
('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), |
||||
('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), |
||||
('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), |
||||
('comment', models.TextField(blank=True, verbose_name='Comment')), |
||||
('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), |
||||
('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), |
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), |
||||
('version', models.IntegerField(default=1, verbose_name='Version')), |
||||
('history_id', models.AutoField(primary_key=True, serialize=False)), |
||||
('history_date', models.DateTimeField()), |
||||
('history_change_reason', models.CharField(max_length=100, null=True)), |
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), |
||||
('app', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='applications.application', verbose_name='Database')), |
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), |
||||
('systemuser', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.systemuser', verbose_name='System user')), |
||||
], |
||||
options={ |
||||
'verbose_name': 'historical Account', |
||||
'ordering': ('-history_date', '-history_id'), |
||||
'get_latest_by': 'history_date', |
||||
}, |
||||
bases=(simple_history.models.HistoricalChanges, models.Model), |
||||
), |
||||
migrations.CreateModel( |
||||
name='Account', |
||||
fields=[ |
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), |
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), |
||||
('name', models.CharField(max_length=128, verbose_name='Name')), |
||||
('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), |
||||
('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), |
||||
('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), |
||||
('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), |
||||
('comment', models.TextField(blank=True, verbose_name='Comment')), |
||||
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), |
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), |
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), |
||||
('version', models.IntegerField(default=1, verbose_name='Version')), |
||||
('app', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='applications.application', verbose_name='Database')), |
||||
('systemuser', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.systemuser', verbose_name='System user')), |
||||
], |
||||
options={ |
||||
'verbose_name': 'Account', |
||||
'unique_together': {('username', 'app', 'systemuser')}, |
||||
}, |
||||
bases=(models.Model, assets.models.base.AuthMixin), |
||||
), |
||||
] |
@ -0,0 +1,40 @@
|
||||
# Generated by Django 3.1.12 on 2021-08-26 09:59 |
||||
|
||||
from django.db import migrations, transaction |
||||
from django.db.models import F |
||||
|
||||
|
||||
def migrate_app_account(apps, schema_editor): |
||||
db_alias = schema_editor.connection.alias |
||||
app_perm_model = apps.get_model("perms", "ApplicationPermission") |
||||
app_account_model = apps.get_model("applications", 'Account') |
||||
|
||||
queryset = app_perm_model.objects \ |
||||
.exclude(system_users__isnull=True) \ |
||||
.exclude(applications__isnull=True) \ |
||||
.annotate(systemuser=F('system_users')) \ |
||||
.annotate(app=F('applications')) \ |
||||
.values('app', 'systemuser', 'org_id') |
||||
|
||||
accounts = [] |
||||
for p in queryset: |
||||
if not p['app']: |
||||
continue |
||||
account = app_account_model( |
||||
app_id=p['app'], systemuser_id=p['systemuser'], |
||||
version=1, org_id=p['org_id'] |
||||
) |
||||
accounts.append(account) |
||||
|
||||
app_account_model.objects.using(db_alias).bulk_create(accounts, ignore_conflicts=True) |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('applications', '0010_appaccount_historicalappaccount'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RunPython(migrate_app_account) |
||||
] |
@ -1 +1,2 @@
|
||||
from .application import * |
||||
from .account import * |
||||
|
@ -0,0 +1,88 @@
|
||||
from django.db import models |
||||
from simple_history.models import HistoricalRecords |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
from common.utils import lazyproperty |
||||
from assets.models.base import BaseUser |
||||
|
||||
|
||||
class Account(BaseUser): |
||||
app = models.ForeignKey('applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Database')) |
||||
systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) |
||||
version = models.IntegerField(default=1, verbose_name=_('Version')) |
||||
history = HistoricalRecords() |
||||
|
||||
auth_attrs = ['username', 'password', 'private_key', 'public_key'] |
||||
|
||||
class Meta: |
||||
verbose_name = _('Account') |
||||
unique_together = [('username', 'app', 'systemuser')] |
||||
|
||||
def __init__(self, *args, **kwargs): |
||||
super().__init__(*args, **kwargs) |
||||
self.auth_snapshot = {} |
||||
|
||||
def get_or_systemuser_attr(self, attr): |
||||
val = getattr(self, attr, None) |
||||
if val: |
||||
return val |
||||
if self.systemuser: |
||||
return getattr(self.systemuser, attr, '') |
||||
return '' |
||||
|
||||
def load_auth(self): |
||||
for attr in self.auth_attrs: |
||||
value = self.get_or_systemuser_attr(attr) |
||||
self.auth_snapshot[attr] = [getattr(self, attr), value] |
||||
setattr(self, attr, value) |
||||
|
||||
def unload_auth(self): |
||||
if not self.systemuser: |
||||
return |
||||
|
||||
for attr, values in self.auth_snapshot.items(): |
||||
origin_value, loaded_value = values |
||||
current_value = getattr(self, attr, '') |
||||
if current_value == loaded_value: |
||||
setattr(self, attr, origin_value) |
||||
|
||||
def save(self, *args, **kwargs): |
||||
self.unload_auth() |
||||
instance = super().save(*args, **kwargs) |
||||
self.load_auth() |
||||
return instance |
||||
|
||||
@lazyproperty |
||||
def category(self): |
||||
return self.app.category |
||||
|
||||
@lazyproperty |
||||
def type(self): |
||||
return self.app.type |
||||
|
||||
@lazyproperty |
||||
def app_display(self): |
||||
return self.systemuser.name |
||||
|
||||
@property |
||||
def username_display(self): |
||||
return self.get_or_systemuser_attr('username') or '' |
||||
|
||||
@lazyproperty |
||||
def systemuser_display(self): |
||||
if not self.systemuser: |
||||
return '' |
||||
return str(self.systemuser) |
||||
|
||||
@property |
||||
def smart_name(self): |
||||
username = self.username_display |
||||
|
||||
if self.app: |
||||
app = str(self.app) |
||||
else: |
||||
app = '*' |
||||
return '{}@{}'.format(username, app) |
||||
|
||||
def __str__(self): |
||||
return self.smart_name |
Binary file not shown.
@ -1,2 +1,3 @@
|
||||
from . import common |
||||
from . import asset_permission |
||||
from . import app_permission |
||||
from . import refresh_perms |
||||
|
@ -0,0 +1,104 @@
|
||||
import itertools |
||||
|
||||
from django.db.models.signals import m2m_changed |
||||
from django.dispatch import receiver |
||||
|
||||
from users.models import User, UserGroup |
||||
from assets.models import SystemUser |
||||
from applications.models import Application |
||||
from common.utils import get_logger |
||||
from common.exceptions import M2MReverseNotAllowed |
||||
from common.decorator import on_transaction_commit |
||||
from common.const.signals import POST_ADD |
||||
from perms.models import ApplicationPermission |
||||
from applications.models import Account as AppAccount |
||||
|
||||
|
||||
logger = get_logger(__file__) |
||||
|
||||
|
||||
@receiver(m2m_changed, sender=ApplicationPermission.applications.through) |
||||
@on_transaction_commit |
||||
def on_app_permission_applications_changed(sender, instance, action, reverse, pk_set, **kwargs): |
||||
if reverse: |
||||
raise M2MReverseNotAllowed |
||||
if action != POST_ADD: |
||||
return |
||||
|
||||
logger.debug("Application permission applications change signal received") |
||||
system_users = instance.system_users.all() |
||||
set_remote_app_asset_system_users_if_need(instance, system_users=system_users) |
||||
|
||||
apps = Application.objects.filter(pk__in=pk_set) |
||||
set_app_accounts(apps, system_users) |
||||
|
||||
|
||||
def set_app_accounts(apps, system_users): |
||||
for app, system_user in itertools.product(apps, system_users): |
||||
AppAccount.objects.get_or_create( |
||||
defaults={'app': app, 'systemuser': system_user}, |
||||
app=app, systemuser=system_user |
||||
) |
||||
|
||||
|
||||
def set_remote_app_asset_system_users_if_need(instance: ApplicationPermission, system_users=None, |
||||
users=None, groups=None): |
||||
if not instance.category_remote_app: |
||||
return |
||||
|
||||
attrs = instance.applications.all().values_list('attrs', flat=True) |
||||
asset_ids = [attr['asset'] for attr in attrs if attr.get('asset')] |
||||
if not asset_ids: |
||||
return |
||||
|
||||
system_users = system_users or instance.system_users.all() |
||||
for system_user in system_users: |
||||
system_user.assets.add(*asset_ids) |
||||
|
||||
if system_user.username_same_with_user: |
||||
users = users or instance.users.all() |
||||
groups = groups or instance.user_groups.all() |
||||
system_user.groups.add(*groups) |
||||
system_user.users.add(*users) |
||||
|
||||
|
||||
@receiver(m2m_changed, sender=ApplicationPermission.system_users.through) |
||||
@on_transaction_commit |
||||
def on_app_permission_system_users_changed(sender, instance, action, reverse, pk_set, **kwargs): |
||||
if reverse: |
||||
raise M2MReverseNotAllowed |
||||
if action != POST_ADD: |
||||
return |
||||
|
||||
logger.debug("Application permission system_users change signal received") |
||||
system_users = SystemUser.objects.filter(pk__in=pk_set) |
||||
|
||||
set_remote_app_asset_system_users_if_need(instance, system_users=system_users) |
||||
apps = instance.applications.all() |
||||
set_app_accounts(apps, system_users) |
||||
|
||||
|
||||
@receiver(m2m_changed, sender=ApplicationPermission.users.through) |
||||
@on_transaction_commit |
||||
def on_app_permission_users_changed(sender, instance, action, reverse, pk_set, **kwargs): |
||||
if reverse: |
||||
raise M2MReverseNotAllowed |
||||
if action != POST_ADD: |
||||
return |
||||
|
||||
logger.debug("Application permission users change signal received") |
||||
users = User.objects.filter(pk__in=pk_set) |
||||
set_remote_app_asset_system_users_if_need(instance, users=users) |
||||
|
||||
|
||||
@receiver(m2m_changed, sender=ApplicationPermission.user_groups.through) |
||||
@on_transaction_commit |
||||
def on_app_permission_user_groups_changed(sender, instance, action, reverse, pk_set, **kwargs): |
||||
if reverse: |
||||
raise M2MReverseNotAllowed |
||||
if action != POST_ADD: |
||||
return |
||||
|
||||
logger.debug("Application permission user groups change signal received") |
||||
groups = UserGroup.objects.filter(pk__in=pk_set) |
||||
set_remote_app_asset_system_users_if_need(instance, groups=groups) |
Loading…
Reference in new issue