mirror of
				https://github.com/jumpserver/jumpserver.git
				synced 2025-10-30 21:30:49 +00:00 
			
		
		
		
	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>
This commit is contained in:
		| @@ -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: |         for acl in acls: | ||||||
|             return False, acl |             if acl.is_action(LoginACL.ActionChoices.confirm) and not acl.reviewers.exists(): | ||||||
|  |                 continue | ||||||
|             ip_group = acl.rules.get('ip_group') |             ip_group = acl.rules.get('ip_group') | ||||||
|             time_periods = acl.rules.get('time_period') |             time_periods = acl.rules.get('time_period') | ||||||
|             is_contain_ip = contains_ip(ip, ip_group) |             is_contain_ip = contains_ip(ip, ip_group) | ||||||
|             is_contain_time_period = contains_time_period(time_periods) |             is_contain_time_period = contains_time_period(time_periods) | ||||||
|         return is_contain_ip and is_contain_time_period, acl |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def allow_user_to_login(user, ip): |  | ||||||
|         acl = LoginACL.filter_acl(user).exclude( |  | ||||||
|             action=LoginACL.ActionChoices.confirm |  | ||||||
|         ).first() |  | ||||||
|         if not acl: |  | ||||||
|             return True, '' |  | ||||||
|         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) |  | ||||||
|  |  | ||||||
|         reject_type = '' |  | ||||||
|             if is_contain_ip and is_contain_time_period: |             if is_contain_ip and is_contain_time_period: | ||||||
|             # 满足条件 |                 # 满足条件,则返回 | ||||||
|             allow = acl.action_allow |                 return acl | ||||||
|             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 "用户不能更新自己的字段: {}" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user