merge: with dev

pull/10583/head
ibuler 2023-05-30 09:47:59 +08:00
commit 7a39552bb2
21 changed files with 163 additions and 79 deletions

View File

@ -1,6 +1,6 @@
# Generated by Django 3.2.17 on 2023-04-25 09:04
from django.db import migrations
from django.db import migrations, models
import common.db.fields
@ -14,7 +14,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='commandfilteracl',
name='new_accounts',
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Account', verbose_name='Accounts'),
field=models.JSONField(default=list, verbose_name='Accounts'),
),
migrations.AddField(
model_name='commandfilteracl',
@ -29,7 +29,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='loginassetacl',
name='new_accounts',
field=common.db.fields.JSONManyToManyField(default=dict, to='assets.Account', verbose_name='Accounts'),
field=models.JSONField(default=list, verbose_name='Accounts')
),
migrations.AddField(
model_name='loginassetacl',

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2023-05-26 09:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('acls', '0013_auto_20230426_1759'),
]
operations = [
migrations.AddField(
model_name='loginassetacl',
name='rules',
field=models.JSONField(default=dict, verbose_name='Rule'),
),
]

View File

@ -4,6 +4,8 @@ from django.utils.translation import gettext_lazy as _
from common.db.fields import JSONManyToManyField
from common.db.models import JMSBaseModel
from common.utils import contains_ip
from common.utils.time_period import contains_time_period
from orgs.mixins.models import OrgModelMixin
__all__ = [
@ -52,11 +54,35 @@ class BaseACL(JMSBaseModel):
def is_action(self, action):
return self.action == action
@classmethod
def get_user_acls(cls, user):
return cls.objects.none()
@classmethod
def get_match_rule_acls(cls, user, ip, acl_qs=None):
if acl_qs is None:
acl_qs = cls.get_user_acls(user)
if not acl_qs:
return
for acl in acl_qs:
if acl.is_action(ActionChoices.review) and not acl.reviewers.exists():
continue
ip_group = acl.rules.get('ip_group')
time_periods = acl.rules.get('time_period')
is_contain_ip = contains_ip(ip, ip_group) if ip_group else True
is_contain_time_period = contains_time_period(time_periods) if time_periods else True
if is_contain_ip and is_contain_time_period:
# 满足条件,则返回
return acl
return None
class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
users = JSONManyToManyField('users.User', default=dict, verbose_name=_('Users'))
assets = JSONManyToManyField('assets.Asset', default=dict, verbose_name=_('Assets'))
accounts = models.JSONField(default=list, verbose_name=_("Account"))
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
class Meta(BaseACL.Meta):
unique_together = ('name', 'org_id')
@ -85,4 +111,4 @@ class UserAssetAccountBaseACL(BaseACL, OrgModelMixin):
kwargs['org_id'] = org_id
if kwargs:
queryset = queryset.filter(**kwargs)
return queryset.distinct()
return queryset.valid().distinct().order_by('priority', 'date_created')

View File

@ -2,15 +2,14 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.utils import get_request_ip, get_ip_city
from common.utils.ip import contains_ip
from common.utils.time_period import contains_time_period
from common.utils.timezone import local_now_display
from .base import BaseACL
class LoginACL(BaseACL):
user = models.ForeignKey(
'users.User', on_delete=models.CASCADE, related_name='login_acls', verbose_name=_('User')
'users.User', on_delete=models.CASCADE,
related_name='login_acls', verbose_name=_('User')
)
# 规则, ip_group, time_period
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
@ -29,23 +28,9 @@ class LoginACL(BaseACL):
def filter_acl(cls, user):
return user.login_acls.all().valid().distinct()
@staticmethod
def match(user, ip):
acl_qs = LoginACL.filter_acl(user)
if not acl_qs:
return
for acl in acl_qs:
if acl.is_action(LoginACL.ActionChoices.review) and \
not acl.reviewers.exists():
continue
ip_group = acl.rules.get('ip_group')
time_periods = acl.rules.get('time_period')
is_contain_ip = contains_ip(ip, ip_group)
is_contain_time_period = contains_time_period(time_periods)
if is_contain_ip and is_contain_time_period:
# 满足条件,则返回
return acl
@classmethod
def get_user_acls(cls, user):
return cls.filter_acl(user)
def create_confirm_ticket(self, request):
from tickets import const

View File

@ -1,9 +1,13 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .base import UserAssetAccountBaseACL
class LoginAssetACL(UserAssetAccountBaseACL):
# 规则, ip_group, time_period
rules = models.JSONField(default=dict, verbose_name=_('Rule'))
class Meta(UserAssetAccountBaseACL.Meta):
verbose_name = _('Login asset acl')
abstract = False

View File

@ -1,11 +1,20 @@
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from django.utils.translation import gettext_lazy as _
from common.serializers import MethodSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer
from .rules import RuleSerializer
from ..models import LoginAssetACL
__all__ = ["LoginAssetACLSerializer"]
class LoginAssetACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer):
rules = MethodSerializer(label=_('Rule'))
class Meta(BaseSerializer.Meta):
model = LoginAssetACL
fields = BaseSerializer.Meta.fields + ['rules']
def get_rules_serializer(self):
return RuleSerializer()

View File

@ -7,6 +7,6 @@ type:
- windows
i18n:
Gather facts windows:
zh: 从 Windows 获取信息
zh: 从 Windows 获取硬件信息
en: Gather facts windows
ja: Windowsから事実を取得する

View File

@ -1,12 +1,12 @@
# Generated by Django 2.1.7 on 2019-02-28 09:15
from django.db import migrations, models, connection
import django.utils.timezone
import uuid
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audits', '0004_operatelog_passwordchangelog_userloginlog'),
('users', '0019_auto_20190304_1459'),
@ -22,7 +22,7 @@ class Migration(migrations.Migration):
('type',
models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')],
max_length=2, verbose_name='Login type')),
('ip', models.GenericIPAddressField(verbose_name='Login ip')),
('ip', models.GenericIPAddressField(verbose_name='Login IP')),
('city', models.CharField(blank=True, max_length=254, null=True,
verbose_name='Login city')),
('user_agent',

View File

@ -158,7 +158,7 @@ class UserLoginLog(models.Model):
type = models.CharField(
choices=LoginTypeChoices.choices, max_length=2, verbose_name=_("Login type")
)
ip = models.GenericIPAddressField(verbose_name=_("Login ip"))
ip = models.GenericIPAddressField(verbose_name=_("Login IP"))
city = models.CharField(
max_length=254, blank=True, null=True, verbose_name=_("Login city")
)

View File

@ -16,7 +16,7 @@ from rest_framework.response import Response
from common.api import JMSModelViewSet
from common.exceptions import JMSException
from common.utils import random_string, get_logger
from common.utils import random_string, get_logger, get_request_ip
from common.utils.django import get_request_os
from common.utils.http import is_true, is_false
from orgs.mixins.api import RootOrgViewMixin
@ -311,7 +311,9 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
def _validate_acl(self, user, asset, account):
from acls.models import LoginAssetACL
acl = LoginAssetACL.filter_queryset(user, asset, account).valid().first()
acls = LoginAssetACL.filter_queryset(user, asset, account)
ip = get_request_ip(self.request)
acl = LoginAssetACL.get_match_rule_acls(user, ip, acls)
if not acl:
return
if acl.is_action(acl.ActionChoices.accept):

View File

@ -340,15 +340,14 @@ class AuthACLMixin:
def _check_login_acl(self, user, ip):
# ACL 限制用户登录
acl = LoginACL.match(user, ip)
acl = LoginACL.get_match_rule_acls(user, ip)
if not acl:
return
acl: LoginACL
if acl.is_action(acl.ActionChoices.accept):
if acl.is_action(LoginACL.ActionChoices.accept):
return
if acl.is_action(acl.ActionChoices.reject):
if acl.is_action(LoginACL.ActionChoices.reject):
raise errors.LoginACLIPAndTimePeriodNotAllowed(user.username, request=self.request)
if acl.is_action(acl.ActionChoices.review):

View File

@ -1,14 +1,16 @@
from common.utils.timezone import local_now
def contains_time_period(time_periods):
def contains_time_period(time_periods, ctime=None):
"""
time_periods: [{"id": 1, "value": "00:00~07:30、10:00~13:00"}, {"id": 2, "value": "00:00~00:00"}]
"""
if not time_periods:
return False
return None
current_time = local_now().strftime('%H:%M')
if ctime is None:
ctime = local_now()
current_time = ctime.strftime('%H:%M')
today_time_period = next(filter(lambda x: str(x['id']) == local_now().strftime("%w"), time_periods))
today_time_period = today_time_period['value']
if not today_time_period:

View File

@ -221,7 +221,7 @@ msgstr "ソース ID"
#: accounts/models/account.py:61
#: accounts/serializers/automations/change_secret.py:113
#: accounts/serializers/automations/change_secret.py:133 acls/models/base.py:59
#: accounts/serializers/automations/change_secret.py:133
#: acls/serializers/base.py:77 assets/serializers/asset/common.py:125
#: assets/serializers/gateway.py:28 audits/models.py:49 ops/models/base.py:18
#: perms/models/asset_permission.py:70 perms/serializers/permission.py:39
@ -820,6 +820,12 @@ msgstr "アクティブ"
msgid "Users"
msgstr "ユーザー"
#: acls/models/base.py:59 assets/models/automations/base.py:17
#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:305
#: rbac/tree.py:35
msgid "Accounts"
msgstr "アカウント"
#: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60
#: ops/serializers/job.py:55 terminal/const.py:68
#: terminal/models/session/session.py:43 terminal/serializers/command.py:18
@ -876,10 +882,16 @@ msgid "Login confirm"
msgstr "ログイン確認"
#: acls/models/login_asset_acl.py:8
#, fuzzy
#| msgid "User ID"
msgid "User IP"
msgstr "ユーザーID"
#: acls/models/login_asset_acl.py:11
msgid "Login asset acl"
msgstr "ログインasset acl"
#: acls/models/login_asset_acl.py:18 tickets/const.py:12
#: acls/models/login_asset_acl.py:21 tickets/const.py:12
msgid "Login asset confirm"
msgstr "ログイン資産の確認"
@ -917,6 +929,11 @@ msgstr "いずれのレビューアも組織 '{}' に属していません"
msgid "Command group amount"
msgstr "コマンドグループ数"
#: acls/serializers/login_asset_acl.py:12 audits/models.py:161
#: tickets/models/ticket/login_confirm.py:10
msgid "Login IP"
msgstr "ログインIP"
#: acls/serializers/rules/rules.py:20
#: xpack/plugins/cloud/serializers/task.py:22
msgid "IP address invalid: `{}`"
@ -1331,11 +1348,6 @@ msgstr "パスワードセレクター"
msgid "Submit selector"
msgstr "ボタンセレクターを確認する"
#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38
#: assets/serializers/asset/common.py:305 rbac/tree.py:35
msgid "Accounts"
msgstr "アカウント"
#: assets/models/automations/base.py:22 ops/models/job.py:187
#: settings/serializers/auth/sms.py:100
msgid "Parameters"
@ -2004,10 +2016,6 @@ msgstr "パスワード変更ログ"
msgid "Login type"
msgstr "ログインタイプ"
#: audits/models.py:161 tickets/models/ticket/login_confirm.py:10
msgid "Login ip"
msgstr "ログインIP"
#: audits/models.py:163
#: authentication/templates/authentication/_msg_different_city.html:11
#: tickets/models/ticket/login_confirm.py:11
@ -5754,7 +5762,7 @@ msgid "Redis port"
msgstr "Redis ポート"
#: terminal/models/component/endpoint.py:29
#: terminal/models/component/endpoint.py:98 terminal/serializers/endpoint.py:71
#: terminal/models/component/endpoint.py:98 terminal/serializers/endpoint.py:68
#: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50
#: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90
#: terminal/serializers/storage.py:98
@ -6055,11 +6063,21 @@ msgstr ""
#: terminal/serializers/endpoint.py:64
msgid ""
"The assets within this IP range, the following endpoint will be used for the "
"connection"
msgstr "このIP範囲内のアセットは、以下のエンドポイントを使用して接続されます"
#: terminal/serializers/endpoint.py:60
msgid ""
"If asset IP addresses under different endpoints conflict, use asset labels"
msgstr ""
"異なるエンドポイントの下に競合するアセットIPがある場合は、アセットタグを使用"
"して実装します"
#: terminal/serializers/endpoint.py:64
msgid "Asset IP"
msgstr "資産 IP"
#: terminal/serializers/session.py:24 terminal/serializers/session.py:46
msgid "Can replay"
msgstr "再生できます"

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4819183bc06cc0e33c741be855881d093e2936f99dadadde38bfdb8ef1cae4dd
size 115922
oid sha256:f4cf57efef290fb80f5159a628b8cde7861cb2bd3fcc247fcf7255fbd2757fb9
size 115892

View File

@ -220,7 +220,7 @@ msgstr "来源 ID"
#: accounts/models/account.py:61
#: accounts/serializers/automations/change_secret.py:113
#: accounts/serializers/automations/change_secret.py:133 acls/models/base.py:59
#: accounts/serializers/automations/change_secret.py:133
#: acls/serializers/base.py:77 assets/serializers/asset/common.py:125
#: assets/serializers/gateway.py:28 audits/models.py:49 ops/models/base.py:18
#: perms/models/asset_permission.py:70 perms/serializers/permission.py:39
@ -816,6 +816,12 @@ msgstr "激活中"
msgid "Users"
msgstr "用户管理"
#: acls/models/base.py:59 assets/models/automations/base.py:17
#: assets/models/cmd_filter.py:38 assets/serializers/asset/common.py:305
#: rbac/tree.py:35
msgid "Accounts"
msgstr "账号管理"
#: acls/models/command_acl.py:16 assets/models/cmd_filter.py:60
#: ops/serializers/job.py:55 terminal/const.py:68
#: terminal/models/session/session.py:43 terminal/serializers/command.py:18
@ -872,10 +878,16 @@ msgid "Login confirm"
msgstr "登录复核"
#: acls/models/login_asset_acl.py:8
#, fuzzy
#| msgid "User ID"
msgid "User IP"
msgstr "用户 ID"
#: acls/models/login_asset_acl.py:11
msgid "Login asset acl"
msgstr "登录资产访问控制"
#: acls/models/login_asset_acl.py:18 tickets/const.py:12
#: acls/models/login_asset_acl.py:21 tickets/const.py:12
msgid "Login asset confirm"
msgstr "登录资产复核"
@ -912,6 +924,11 @@ msgstr "所有复核人都不属于组织 `{}`"
msgid "Command group amount"
msgstr "命令组数量"
#: acls/serializers/login_asset_acl.py:12 audits/models.py:161
#: tickets/models/ticket/login_confirm.py:10
msgid "Login IP"
msgstr "登录 IP"
#: acls/serializers/rules/rules.py:20
#: xpack/plugins/cloud/serializers/task.py:22
msgid "IP address invalid: `{}`"
@ -929,7 +946,7 @@ msgstr ""
#: authentication/templates/authentication/_msg_oauth_bind.html:12
#: authentication/templates/authentication/_msg_rest_password_success.html:8
#: authentication/templates/authentication/_msg_rest_public_key_success.html:8
#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:67
#: settings/serializers/terminal.py:10
msgid "IP"
msgstr "IP"
@ -1324,11 +1341,6 @@ msgstr "密码选择器"
msgid "Submit selector"
msgstr "确认按钮选择器"
#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38
#: assets/serializers/asset/common.py:305 rbac/tree.py:35
msgid "Accounts"
msgstr "账号管理"
#: assets/models/automations/base.py:22 ops/models/job.py:187
#: settings/serializers/auth/sms.py:100
msgid "Parameters"
@ -1993,10 +2005,6 @@ msgstr "改密日志"
msgid "Login type"
msgstr "登录方式"
#: audits/models.py:161 tickets/models/ticket/login_confirm.py:10
msgid "Login ip"
msgstr "登录IP"
#: audits/models.py:163
#: authentication/templates/authentication/_msg_different_city.html:11
#: tickets/models/ticket/login_confirm.py:11
@ -5673,7 +5681,7 @@ msgid "Redis port"
msgstr "Redis 端口"
#: terminal/models/component/endpoint.py:29
#: terminal/models/component/endpoint.py:98 terminal/serializers/endpoint.py:71
#: terminal/models/component/endpoint.py:98 terminal/serializers/endpoint.py:68
#: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50
#: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90
#: terminal/serializers/storage.py:98
@ -5971,9 +5979,19 @@ msgstr ""
#: terminal/serializers/endpoint.py:64
msgid ""
"The assets within this IP range, the following endpoint will be used for the "
"connection"
msgstr "该 IP 范围内的资产,将使用下面的端点进行连接"
#: terminal/serializers/endpoint.py:60
msgid ""
"If asset IP addresses under different endpoints conflict, use asset labels"
msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现"
#: terminal/serializers/endpoint.py:64
msgid "Asset IP"
msgstr "资产 IP"
#: terminal/serializers/session.py:24 terminal/serializers/session.py:46
msgid "Can replay"
msgstr "是否可重放"

View File

@ -1,4 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from acls.serializers.rules import ip_group_child_validator, ip_group_help_text
@ -17,7 +17,8 @@ class EndpointSerializer(BulkModelSerializer):
max_length=128, default=db_port_manager.oracle_port_range, read_only=True,
label=_('Oracle port range'),
help_text=_(
'Oracle proxy server listen port is dynamic, Each additional Oracle database instance adds a port listener'
'Oracle proxy server listen port is dynamic, Each additional Oracle '
'database instance adds a port listener'
)
)
@ -59,12 +60,13 @@ class EndpointSerializer(BulkModelSerializer):
class EndpointRuleSerializer(BulkModelSerializer):
_ip_group_help_text = '{} <br> {}'.format(
_ip_group_help_text = '{}, {} <br>{}'.format(
_('The assets within this IP range, the following endpoint will be used for the connection'),
_('If asset IP addresses under different endpoints conflict, use asset labels'),
ip_group_help_text,
_('If asset IP addresses under different endpoints conflict, use asset labels')
)
ip_group = serializers.ListField(
default=['*'], label=_('IP'), help_text=_ip_group_help_text,
default=['*'], label=_('Asset IP'), help_text=_ip_group_help_text,
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator])
)
endpoint = ObjectRelatedField(
@ -80,4 +82,5 @@ class EndpointRuleSerializer(BulkModelSerializer):
'comment', 'date_created', 'date_updated', 'created_by'
]
extra_kwargs = {
'priority': {'default': 50}
}

View File

@ -1,8 +1,8 @@
# Generated by Django 3.1.14 on 2022-06-09 09:58
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
@ -21,7 +21,7 @@ class Migration(migrations.Migration):
('ticket_ptr',
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
primary_key=True, serialize=False, to='tickets.ticket')),
('apply_login_ip', models.GenericIPAddressField(null=True, verbose_name='Login ip')),
('apply_login_ip', models.GenericIPAddressField(null=True, verbose_name='Login IP')),
('apply_login_city', models.CharField(max_length=64, null=True, verbose_name='Login city')),
('apply_login_datetime', models.DateTimeField(null=True, verbose_name='Login datetime')),
],

View File

@ -12,7 +12,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='applyassetticket',
name='apply_actions',
field=models.IntegerField(default=31, verbose_name='Actions'),
field=models.IntegerField(default=1, verbose_name='Actions'),
),
migrations.AlterField(
model_name='approvalrule',

View File

@ -15,7 +15,7 @@ class ApplyAssetTicket(Ticket):
# 申请信息
apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Asset'))
apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts'))
apply_actions = models.IntegerField(verbose_name=_('Actions'), default=ActionChoices.all())
apply_actions = models.IntegerField(verbose_name=_('Actions'), default=ActionChoices.connect)
apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True)
apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True)

View File

@ -7,6 +7,6 @@ __all__ = ['ApplyLoginTicket']
class ApplyLoginTicket(Ticket):
apply_login_ip = models.GenericIPAddressField(verbose_name=_('Login ip'), null=True)
apply_login_ip = models.GenericIPAddressField(verbose_name=_('Login IP'), null=True)
apply_login_city = models.CharField(max_length=64, verbose_name=_('Login city'), null=True)
apply_login_datetime = models.DateTimeField(verbose_name=_('Login datetime'), null=True)

View File

@ -97,7 +97,7 @@ class Migration(migrations.Migration):
('username', models.CharField(max_length=20, verbose_name='Username')),
('type',
models.CharField(choices=[('W', 'Web'), ('T', 'Terminal')], max_length=2, verbose_name='Login type')),
('ip', models.GenericIPAddressField(verbose_name='Login ip')),
('ip', models.GenericIPAddressField(verbose_name='Login IP')),
('city', models.CharField(blank=True, max_length=254, null=True, verbose_name='Login city')),
('user_agent', models.CharField(blank=True, max_length=254, null=True, verbose_name='User agent')),
('datetime', models.DateTimeField(auto_now_add=True, verbose_name='Date login')),