mirror of https://github.com/jumpserver/jumpserver
perf: 优化用户登录ACL根据规则优先级进行匹配 (#8672)
* perf: 优化用户登录ACL根据规则优先级进行匹配 * perf: 修改冲突 Co-authored-by: Jiangjie.Bai <bugatti_it@163.com> Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>pull/8682/head
parent
ee1aff243c
commit
429e838973
|
@ -44,58 +44,29 @@ class LoginACL(BaseACL):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
def is_action(self, action):
|
||||||
def action_reject(self):
|
return self.action == action
|
||||||
return self.action == self.ActionChoices.reject
|
|
||||||
|
|
||||||
@property
|
|
||||||
def action_allow(self):
|
|
||||||
return self.action == self.ActionChoices.allow
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter_acl(cls, user):
|
def filter_acl(cls, user):
|
||||||
return user.login_acls.all().valid().distinct()
|
return user.login_acls.all().valid().distinct()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def allow_user_confirm_if_need(user, ip):
|
def match(user, ip):
|
||||||
acl = LoginACL.filter_acl(user).filter(
|
acls = LoginACL.filter_acl(user)
|
||||||
action=LoginACL.ActionChoices.confirm
|
if not acls:
|
||||||
).first()
|
return
|
||||||
acl = acl if acl and acl.reviewers.exists() else None
|
|
||||||
if not acl:
|
|
||||||
return False, acl
|
|
||||||
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)
|
|
||||||
return is_contain_ip and is_contain_time_period, acl
|
|
||||||
|
|
||||||
@staticmethod
|
for acl in acls:
|
||||||
def allow_user_to_login(user, ip):
|
if acl.is_action(LoginACL.ActionChoices.confirm) and not acl.reviewers.exists():
|
||||||
acl = LoginACL.filter_acl(user).exclude(
|
continue
|
||||||
action=LoginACL.ActionChoices.confirm
|
ip_group = acl.rules.get('ip_group')
|
||||||
).first()
|
time_periods = acl.rules.get('time_period')
|
||||||
if not acl:
|
is_contain_ip = contains_ip(ip, ip_group)
|
||||||
return True, ''
|
is_contain_time_period = contains_time_period(time_periods)
|
||||||
ip_group = acl.rules.get('ip_group')
|
if is_contain_ip and is_contain_time_period:
|
||||||
time_periods = acl.rules.get('time_period')
|
# 满足条件,则返回
|
||||||
is_contain_ip = contains_ip(ip, ip_group)
|
return acl
|
||||||
is_contain_time_period = contains_time_period(time_periods)
|
|
||||||
|
|
||||||
reject_type = ''
|
|
||||||
if is_contain_ip and is_contain_time_period:
|
|
||||||
# 满足条件
|
|
||||||
allow = acl.action_allow
|
|
||||||
if not allow:
|
|
||||||
reject_type = 'ip' if is_contain_ip else 'time'
|
|
||||||
else:
|
|
||||||
# 不满足条件
|
|
||||||
# 如果acl本身允许,那就拒绝;如果本身拒绝,那就允许
|
|
||||||
allow = not acl.action_allow
|
|
||||||
if not allow:
|
|
||||||
reject_type = 'ip' if not is_contain_ip else 'time'
|
|
||||||
|
|
||||||
return allow, reject_type
|
|
||||||
|
|
||||||
def create_confirm_ticket(self, request):
|
def create_confirm_ticket(self, request):
|
||||||
from tickets import const
|
from tickets import const
|
||||||
|
|
|
@ -138,18 +138,11 @@ class ACLError(AuthFailedNeedLogMixin, AuthFailedError):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LoginIPNotAllowed(ACLError):
|
class LoginACLNotAllowed(ACLError):
|
||||||
def __init__(self, username, request, **kwargs):
|
def __init__(self, username, request, **kwargs):
|
||||||
self.username = username
|
self.username = username
|
||||||
self.request = request
|
self.request = request
|
||||||
super().__init__(_("IP is not allowed"), **kwargs)
|
super().__init__(_("ACL is not allowed"), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class TimePeriodNotAllowed(ACLError):
|
|
||||||
def __init__(self, username, request, **kwargs):
|
|
||||||
self.username = username
|
|
||||||
self.request = request
|
|
||||||
super().__init__(_("Time Period is not allowed"), **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class MFACodeRequiredError(AuthFailedError):
|
class MFACodeRequiredError(AuthFailedError):
|
||||||
|
|
|
@ -328,13 +328,56 @@ class AuthACLMixin:
|
||||||
|
|
||||||
def _check_login_acl(self, user, ip):
|
def _check_login_acl(self, user, ip):
|
||||||
# ACL 限制用户登录
|
# ACL 限制用户登录
|
||||||
is_allowed, limit_type = LoginACL.allow_user_to_login(user, ip)
|
acl = LoginACL.match(user, ip)
|
||||||
if is_allowed:
|
if not acl:
|
||||||
return
|
return
|
||||||
if limit_type == 'ip':
|
|
||||||
raise errors.LoginIPNotAllowed(username=user.username, request=self.request)
|
acl: LoginACL
|
||||||
elif limit_type == 'time':
|
if acl.is_action(acl.ActionChoices.allow):
|
||||||
raise errors.TimePeriodNotAllowed(username=user.username, request=self.request)
|
return
|
||||||
|
|
||||||
|
if acl.is_action(acl.ActionChoices.reject):
|
||||||
|
raise errors.LoginACLNotAllowed(username=user.username, request=self.request)
|
||||||
|
|
||||||
|
if acl.is_action(acl.ActionChoices.confirm):
|
||||||
|
self.request.session['auth_confirm_required'] = '1'
|
||||||
|
self.request.session['auth_acl_id'] = str(acl.id)
|
||||||
|
return
|
||||||
|
|
||||||
|
def check_user_login_confirm_if_need(self, user):
|
||||||
|
if not self.request.session.get("auth_confirm_required"):
|
||||||
|
return
|
||||||
|
acl_id = self.request.session.get('auth_acl_id')
|
||||||
|
logger.debug('Login confirm acl id: {}'.format(acl_id))
|
||||||
|
if not acl_id:
|
||||||
|
return
|
||||||
|
acl = LoginACL.filter_acl(user).filter(id=acl_id).first()
|
||||||
|
if not acl:
|
||||||
|
return
|
||||||
|
if not acl.is_action(acl.ActionChoices.confirm):
|
||||||
|
return
|
||||||
|
self.get_ticket_or_create(acl)
|
||||||
|
self.check_user_login_confirm()
|
||||||
|
|
||||||
|
def get_ticket_or_create(self, acl):
|
||||||
|
ticket = self.get_ticket()
|
||||||
|
if not ticket or ticket.is_state(ticket.State.closed):
|
||||||
|
ticket = acl.create_confirm_ticket(self.request)
|
||||||
|
self.request.session['auth_ticket_id'] = str(ticket.id)
|
||||||
|
return ticket
|
||||||
|
|
||||||
|
def check_user_login_confirm(self):
|
||||||
|
ticket = self.get_ticket()
|
||||||
|
if not ticket:
|
||||||
|
raise errors.LoginConfirmOtherError('', "Not found")
|
||||||
|
elif ticket.is_state(ticket.State.approved):
|
||||||
|
self.request.session["auth_confirm_required"] = ''
|
||||||
|
return
|
||||||
|
elif ticket.is_status(ticket.Status.open):
|
||||||
|
raise errors.LoginConfirmWaitError(ticket.id)
|
||||||
|
else:
|
||||||
|
# rejected, closed
|
||||||
|
raise errors.LoginConfirmOtherError(ticket.id, ticket.get_state_display())
|
||||||
|
|
||||||
def get_ticket(self):
|
def get_ticket(self):
|
||||||
from tickets.models import ApplyLoginTicket
|
from tickets.models import ApplyLoginTicket
|
||||||
|
@ -346,44 +389,6 @@ class AuthACLMixin:
|
||||||
ticket = ApplyLoginTicket.all().filter(id=ticket_id).first()
|
ticket = ApplyLoginTicket.all().filter(id=ticket_id).first()
|
||||||
return ticket
|
return ticket
|
||||||
|
|
||||||
def get_ticket_or_create(self, confirm_setting):
|
|
||||||
ticket = self.get_ticket()
|
|
||||||
if not ticket or ticket.is_status(ticket.Status.closed):
|
|
||||||
ticket = confirm_setting.create_confirm_ticket(self.request)
|
|
||||||
self.request.session['auth_ticket_id'] = str(ticket.id)
|
|
||||||
return ticket
|
|
||||||
|
|
||||||
def check_user_login_confirm(self):
|
|
||||||
ticket = self.get_ticket()
|
|
||||||
if not ticket:
|
|
||||||
raise errors.LoginConfirmOtherError('', "Not found")
|
|
||||||
|
|
||||||
if ticket.is_status(ticket.Status.open):
|
|
||||||
raise errors.LoginConfirmWaitError(ticket.id)
|
|
||||||
elif ticket.is_state(ticket.State.approved):
|
|
||||||
self.request.session["auth_confirm"] = "1"
|
|
||||||
return
|
|
||||||
elif ticket.is_state(ticket.State.rejected):
|
|
||||||
raise errors.LoginConfirmOtherError(
|
|
||||||
ticket.id, ticket.get_state_display()
|
|
||||||
)
|
|
||||||
elif ticket.is_state(ticket.State.closed):
|
|
||||||
raise errors.LoginConfirmOtherError(
|
|
||||||
ticket.id, ticket.get_state_display()
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise errors.LoginConfirmOtherError(
|
|
||||||
ticket.id, ticket.get_status_display()
|
|
||||||
)
|
|
||||||
|
|
||||||
def check_user_login_confirm_if_need(self, user):
|
|
||||||
ip = self.get_request_ip()
|
|
||||||
is_allowed, confirm_setting = LoginACL.allow_user_confirm_if_need(user, ip)
|
|
||||||
if self.request.session.get('auth_confirm') or not is_allowed:
|
|
||||||
return
|
|
||||||
self.get_ticket_or_create(confirm_setting)
|
|
||||||
self.check_user_login_confirm()
|
|
||||||
|
|
||||||
|
|
||||||
class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPostCheckMixin):
|
class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPostCheckMixin):
|
||||||
request = None
|
request = None
|
||||||
|
@ -482,7 +487,9 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost
|
||||||
return self.check_user_auth(valid_data)
|
return self.check_user_auth(valid_data)
|
||||||
|
|
||||||
def clear_auth_mark(self):
|
def clear_auth_mark(self):
|
||||||
keys = ['auth_password', 'user_id', 'auth_confirm', 'auth_ticket_id']
|
keys = [
|
||||||
|
'auth_password', 'user_id', 'auth_confirm_required', 'auth_ticket_id', 'auth_acl_id'
|
||||||
|
]
|
||||||
for k in keys:
|
for k in keys:
|
||||||
self.request.session.pop(k, '')
|
self.request.session.pop(k, '')
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7ff3ae18c27279b8783eba9e85b270f9c3da63f812da315ba210877b33b960a8
|
oid sha256:322701b975fe90b4b187c4a99ddd1837291150502c82accf0a4c6e32dddf91be
|
||||||
size 128908
|
size 128721
|
||||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2022-07-28 13:43+0800\n"
|
"POT-Creation-Date: 2022-07-29 10:56+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -80,7 +80,7 @@ msgstr "拒否"
|
||||||
msgid "Allow"
|
msgid "Allow"
|
||||||
msgstr "許可"
|
msgstr "許可"
|
||||||
|
|
||||||
#: acls/models/login_acl.py:20 acls/models/login_acl.py:104
|
#: acls/models/login_acl.py:20 acls/models/login_acl.py:75
|
||||||
#: acls/models/login_asset_acl.py:17 tickets/const.py:9
|
#: acls/models/login_asset_acl.py:17 tickets/const.py:9
|
||||||
msgid "Login confirm"
|
msgid "Login confirm"
|
||||||
msgstr "ログイン確認"
|
msgstr "ログイン確認"
|
||||||
|
@ -1915,7 +1915,7 @@ msgstr "このアカウントは期限切れです"
|
||||||
msgid "Auth backend not match"
|
msgid "Auth backend not match"
|
||||||
msgstr "Authバックエンドが一致しない"
|
msgstr "Authバックエンドが一致しない"
|
||||||
|
|
||||||
#: authentication/errors/const.py:28
|
#: authentication/errors/const.py:28 authentication/errors/failed.py:145
|
||||||
msgid "ACL is not allowed"
|
msgid "ACL is not allowed"
|
||||||
msgstr "ACLは許可されません"
|
msgstr "ACLは許可されません"
|
||||||
|
|
||||||
|
@ -1983,23 +1983,15 @@ msgstr "受け入れのためのログイン確認チケットを待つ"
|
||||||
msgid "Login confirm ticket was {}"
|
msgid "Login confirm ticket was {}"
|
||||||
msgstr "ログイン確認チケットは {} でした"
|
msgstr "ログイン確認チケットは {} でした"
|
||||||
|
|
||||||
#: authentication/errors/failed.py:145
|
#: authentication/errors/failed.py:150
|
||||||
msgid "IP is not allowed"
|
|
||||||
msgstr "IPは許可されていません"
|
|
||||||
|
|
||||||
#: authentication/errors/failed.py:152
|
|
||||||
msgid "Time Period is not allowed"
|
|
||||||
msgstr "期間は許可されていません"
|
|
||||||
|
|
||||||
#: authentication/errors/failed.py:157
|
|
||||||
msgid "Please enter MFA code"
|
msgid "Please enter MFA code"
|
||||||
msgstr "MFAコードを入力してください"
|
msgstr "MFAコードを入力してください"
|
||||||
|
|
||||||
#: authentication/errors/failed.py:162
|
#: authentication/errors/failed.py:155
|
||||||
msgid "Please enter SMS code"
|
msgid "Please enter SMS code"
|
||||||
msgstr "SMSコードを入力してください"
|
msgstr "SMSコードを入力してください"
|
||||||
|
|
||||||
#: authentication/errors/failed.py:167 users/exceptions.py:15
|
#: authentication/errors/failed.py:160 users/exceptions.py:15
|
||||||
msgid "Phone not set"
|
msgid "Phone not set"
|
||||||
msgstr "電話が設定されていない"
|
msgstr "電話が設定されていない"
|
||||||
|
|
||||||
|
@ -6863,5 +6855,11 @@ msgstr "究極のエディション"
|
||||||
msgid "Community edition"
|
msgid "Community edition"
|
||||||
msgstr "コミュニティ版"
|
msgstr "コミュニティ版"
|
||||||
|
|
||||||
|
#~ msgid "IP is not allowed"
|
||||||
|
#~ msgstr "IPは許可されていません"
|
||||||
|
|
||||||
|
#~ msgid "Time Period is not allowed"
|
||||||
|
#~ msgstr "期間は許可されていません"
|
||||||
|
|
||||||
#~ msgid "User cannot self-update fields: {}"
|
#~ msgid "User cannot self-update fields: {}"
|
||||||
#~ msgstr "ユーザーは自分のフィールドを更新できません: {}"
|
#~ msgstr "ユーザーは自分のフィールドを更新できません: {}"
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4c10c6bd05e79bc462db9863136e538978e5ca2644c6fd228050603135559d83
|
oid sha256:9ed12e275e241284573d49c752cf01bafddb912dfe38ae2888a62e62cdb30ebd
|
||||||
size 106223
|
size 106084
|
||||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2022-07-28 13:43+0800\n"
|
"POT-Creation-Date: 2022-07-29 10:56+0800\n"
|
||||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||||
|
@ -79,7 +79,7 @@ msgstr "拒绝"
|
||||||
msgid "Allow"
|
msgid "Allow"
|
||||||
msgstr "允许"
|
msgstr "允许"
|
||||||
|
|
||||||
#: acls/models/login_acl.py:20 acls/models/login_acl.py:104
|
#: acls/models/login_acl.py:20 acls/models/login_acl.py:75
|
||||||
#: acls/models/login_asset_acl.py:17 tickets/const.py:9
|
#: acls/models/login_asset_acl.py:17 tickets/const.py:9
|
||||||
msgid "Login confirm"
|
msgid "Login confirm"
|
||||||
msgstr "登录复核"
|
msgstr "登录复核"
|
||||||
|
@ -1901,7 +1901,7 @@ msgstr "此账号已过期"
|
||||||
msgid "Auth backend not match"
|
msgid "Auth backend not match"
|
||||||
msgstr "没有匹配到认证后端"
|
msgstr "没有匹配到认证后端"
|
||||||
|
|
||||||
#: authentication/errors/const.py:28
|
#: authentication/errors/const.py:28 authentication/errors/failed.py:145
|
||||||
msgid "ACL is not allowed"
|
msgid "ACL is not allowed"
|
||||||
msgstr "ACL 不被允许"
|
msgstr "ACL 不被允许"
|
||||||
|
|
||||||
|
@ -1963,23 +1963,15 @@ msgstr "等待登录复核处理"
|
||||||
msgid "Login confirm ticket was {}"
|
msgid "Login confirm ticket was {}"
|
||||||
msgstr "登录复核: {}"
|
msgstr "登录复核: {}"
|
||||||
|
|
||||||
#: authentication/errors/failed.py:145
|
#: authentication/errors/failed.py:150
|
||||||
msgid "IP is not allowed"
|
|
||||||
msgstr "来源 IP 不被允许登录"
|
|
||||||
|
|
||||||
#: authentication/errors/failed.py:152
|
|
||||||
msgid "Time Period is not allowed"
|
|
||||||
msgstr "该 时间段 不被允许登录"
|
|
||||||
|
|
||||||
#: authentication/errors/failed.py:157
|
|
||||||
msgid "Please enter MFA code"
|
msgid "Please enter MFA code"
|
||||||
msgstr "请输入 MFA 验证码"
|
msgstr "请输入 MFA 验证码"
|
||||||
|
|
||||||
#: authentication/errors/failed.py:162
|
#: authentication/errors/failed.py:155
|
||||||
msgid "Please enter SMS code"
|
msgid "Please enter SMS code"
|
||||||
msgstr "请输入短信验证码"
|
msgstr "请输入短信验证码"
|
||||||
|
|
||||||
#: authentication/errors/failed.py:167 users/exceptions.py:15
|
#: authentication/errors/failed.py:160 users/exceptions.py:15
|
||||||
msgid "Phone not set"
|
msgid "Phone not set"
|
||||||
msgstr "手机号没有设置"
|
msgstr "手机号没有设置"
|
||||||
|
|
||||||
|
@ -6766,5 +6758,11 @@ msgstr "旗舰版"
|
||||||
msgid "Community edition"
|
msgid "Community edition"
|
||||||
msgstr "社区版"
|
msgstr "社区版"
|
||||||
|
|
||||||
|
#~ msgid "IP is not allowed"
|
||||||
|
#~ msgstr "来源 IP 不被允许登录"
|
||||||
|
|
||||||
|
#~ msgid "Time Period is not allowed"
|
||||||
|
#~ msgstr "该 时间段 不被允许登录"
|
||||||
|
|
||||||
#~ msgid "User cannot self-update fields: {}"
|
#~ msgid "User cannot self-update fields: {}"
|
||||||
#~ msgstr "用户不能更新自己的字段: {}"
|
#~ msgstr "用户不能更新自己的字段: {}"
|
||||||
|
|
Loading…
Reference in New Issue