perf: ldap 能多组织同步用户 (#10543)

Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>
pull/10556/head
fit2bot 2023-05-25 17:35:36 +08:00 committed by GitHub
parent fa21c83db3
commit a6366a2dd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 72 additions and 36 deletions

View File

@ -255,7 +255,7 @@ class Config(dict):
'AUTH_LDAP_SYNC_IS_PERIODIC': False, 'AUTH_LDAP_SYNC_IS_PERIODIC': False,
'AUTH_LDAP_SYNC_INTERVAL': None, 'AUTH_LDAP_SYNC_INTERVAL': None,
'AUTH_LDAP_SYNC_CRONTAB': None, 'AUTH_LDAP_SYNC_CRONTAB': None,
'AUTH_LDAP_SYNC_ORG_ID': '00000000-0000-0000-0000-000000000002', 'AUTH_LDAP_SYNC_ORG_IDS': ['00000000-0000-0000-0000-000000000002'],
'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False, 'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False,
'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1, 'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1,

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import os import os
import ldap import ldap
from ..const import CONFIG, PROJECT_DIR, BASE_DIR from ..const import CONFIG, PROJECT_DIR, BASE_DIR
@ -48,7 +49,7 @@ AUTH_LDAP_SEARCH_PAGED_SIZE = CONFIG.AUTH_LDAP_SEARCH_PAGED_SIZE
AUTH_LDAP_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_SYNC_IS_PERIODIC AUTH_LDAP_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_SYNC_IS_PERIODIC
AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL
AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB
AUTH_LDAP_SYNC_ORG_ID = CONFIG.AUTH_LDAP_SYNC_ORG_ID AUTH_LDAP_SYNC_ORG_IDS = CONFIG.AUTH_LDAP_SYNC_ORG_IDS
AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
# ============================================================================== # ==============================================================================

View File

@ -62,7 +62,7 @@ class OrgViewSet(JMSBulkModelViewSet):
msg = _('The current organization ({}) cannot be deleted').format(current_org) msg = _('The current organization ({}) cannot be deleted').format(current_org)
raise PermissionDenied(detail=msg) raise PermissionDenied(detail=msg)
if str(instance.id) == settings.AUTH_LDAP_SYNC_ORG_ID: if str(instance.id) in settings.AUTH_LDAP_SYNC_ORG_IDS:
msg = _( msg = _(
'LDAP synchronization is set to the current organization. ' 'LDAP synchronization is set to the current organization. '
'Please switch to another organization before deleting' 'Please switch to another organization before deleting'

View File

@ -191,13 +191,13 @@ class LDAPUserImportAPI(APIView):
'POST': 'settings.change_auth' 'POST': 'settings.change_auth'
} }
def get_org(self): def get_orgs(self):
org_id = self.request.data.get('org_id') org_ids = self.request.data.get('org_ids')
if is_uuid(org_id): if org_ids:
org = Organization.objects.get(id=org_id) orgs = list(Organization.objects.filter(id__in=org_ids))
else: else:
org = current_org orgs = [current_org]
return org return orgs
def get_ldap_users(self): def get_ldap_users(self):
username_list = self.request.data.get('username_list', []) username_list = self.request.data.get('username_list', [])
@ -219,14 +219,15 @@ class LDAPUserImportAPI(APIView):
if users is None: if users is None:
return Response({'msg': _('Get ldap users is None')}, status=400) return Response({'msg': _('Get ldap users is None')}, status=400)
org = self.get_org() orgs = self.get_orgs()
errors = LDAPImportUtil().perform_import(users, org) errors = LDAPImportUtil().perform_import(users, orgs)
if errors: if errors:
return Response({'errors': errors}, status=400) return Response({'errors': errors}, status=400)
count = users if users is None else len(users) count = users if users is None else len(users)
orgs_name = ', '.join([str(org) for org in orgs])
return Response({ return Response({
'msg': _('Imported {} users successfully (Organization: {})').format(count, org) 'msg': _('Imported {} users successfully (Organization: {})').format(count, orgs_name)
}) })

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.19 on 2023-05-25 09:00
import json
from django.db import migrations
def migrate_ldap_sync_org_ids(apps, schema_editor):
setting_model = apps.get_model("settings", "Setting")
db_alias = schema_editor.connection.alias
instance = setting_model.objects.using(db_alias).filter(name='AUTH_LDAP_SYNC_ORG_ID').first()
if not instance:
return
ldap_sync_org_id = json.loads(instance.value)
setting_model.objects.using(db_alias).update_or_create(
name='AUTH_LDAP_SYNC_ORG_IDS', category='ldap',
value=json.dumps([ldap_sync_org_id])
)
instance.delete()
class Migration(migrations.Migration):
dependencies = [
('settings', '0006_remove_setting_enabled'),
]
operations = [
migrations.RunPython(migrate_ldap_sync_org_ids)
]

View File

@ -59,7 +59,7 @@ class LDAPSettingSerializer(serializers.Serializer):
help_text=_('User attr map present how to map LDAP user attr to ' help_text=_('User attr map present how to map LDAP user attr to '
'jumpserver, username,name,email is jumpserver attr') 'jumpserver, username,name,email is jumpserver attr')
) )
AUTH_LDAP_SYNC_ORG_ID = serializers.CharField( AUTH_LDAP_SYNC_ORG_IDS = serializers.ListField(
required=False, label=_('Organization'), max_length=36 required=False, label=_('Organization'), max_length=36
) )
AUTH_LDAP_SYNC_IS_PERIODIC = serializers.BooleanField( AUTH_LDAP_SYNC_IS_PERIODIC = serializers.BooleanField(

View File

@ -16,7 +16,7 @@ class PrivateSettingSerializer(PublicSettingSerializer):
OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField() OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField()
TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField() TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField()
TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.CharField() TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.CharField()
AUTH_LDAP_SYNC_ORG_ID = serializers.CharField() AUTH_LDAP_SYNC_ORG_IDS = serializers.ListField()
SECURITY_MAX_IDLE_TIME = serializers.IntegerField() SECURITY_MAX_IDLE_TIME = serializers.IntegerField()
SECURITY_VIEW_AUTH_NEED_MFA = serializers.BooleanField() SECURITY_VIEW_AUTH_NEED_MFA = serializers.BooleanField()
SECURITY_MFA_VERIFY_TTL = serializers.IntegerField() SECURITY_MFA_VERIFY_TTL = serializers.IntegerField()

View File

@ -2,8 +2,8 @@
# #
from celery import shared_task from celery import shared_task
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.db import transaction from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from ops.celery.decorator import after_app_ready_start from ops.celery.decorator import after_app_ready_start
@ -30,14 +30,14 @@ def import_ldap_user():
util_import = LDAPImportUtil() util_import = LDAPImportUtil()
users = util_server.search() users = util_server.search()
if settings.XPACK_ENABLED: if settings.XPACK_ENABLED:
org_id = settings.AUTH_LDAP_SYNC_ORG_ID org_ids = settings.AUTH_LDAP_SYNC_ORG_IDS
default_org = None default_org = None
else: else:
# 社区版默认导入Default组织 # 社区版默认导入Default组织
org_id = Organization.DEFAULT_ID org_ids = [Organization.DEFAULT_ID]
default_org = Organization.default() default_org = Organization.default()
org = Organization.get_instance(org_id, default=default_org) orgs = list(set([Organization.get_instance(org_id, default=default_org) for org_id in org_ids]))
errors = util_import.perform_import(users, org) errors = util_import.perform_import(users, orgs)
if errors: if errors:
logger.error("Imported LDAP users errors: {}".format(errors)) logger.error("Imported LDAP users errors: {}".format(errors))
else: else:

View File

@ -1,8 +1,13 @@
# coding: utf-8 # coding: utf-8
# #
import os
import json import json
from collections import defaultdict
from copy import deepcopy
from django.conf import settings
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from ldap3 import Server, Connection, SIMPLE from ldap3 import Server, Connection, SIMPLE
from ldap3.core.exceptions import ( from ldap3.core.exceptions import (
LDAPSocketOpenError, LDAPSocketOpenError,
@ -18,19 +23,14 @@ from ldap3.core.exceptions import (
LDAPConfigurationError, LDAPConfigurationError,
LDAPAttributeError, LDAPAttributeError,
) )
from django.conf import settings
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from copy import deepcopy
from collections import defaultdict
from orgs.utils import tmp_to_org
from common.const import LDAP_AD_ACCOUNT_DISABLE
from common.utils import timeit, get_logger
from common.db.utils import close_old_connections
from users.utils import construct_user_email
from users.models import User, UserGroup
from authentication.backends.ldap import LDAPAuthorizationBackend, LDAPUser from authentication.backends.ldap import LDAPAuthorizationBackend, LDAPUser
from common.const import LDAP_AD_ACCOUNT_DISABLE
from common.db.utils import close_old_connections
from common.utils import timeit, get_logger
from orgs.utils import tmp_to_org
from users.models import User, UserGroup
from users.utils import construct_user_email
logger = get_logger(__file__) logger = get_logger(__file__)
@ -394,7 +394,7 @@ class LDAPImportUtil(object):
group_names.append(group_name) group_names.append(group_name)
return group_names return group_names
def perform_import(self, users, org=None): def perform_import(self, users, orgs):
logger.info('Start perform import ldap users, count: {}'.format(len(users))) logger.info('Start perform import ldap users, count: {}'.format(len(users)))
errors = [] errors = []
objs = [] objs = []
@ -416,13 +416,20 @@ class LDAPImportUtil(object):
errors.append({user['username']: str(e)}) errors.append({user['username']: str(e)})
logger.error(e) logger.error(e)
continue continue
for org in orgs:
self.bind_org(org, objs, group_users_mapper)
logger.info('End perform import ldap users')
return errors
@staticmethod
def bind_org(org, users, group_users_mapper):
if not org: if not org:
return return
if org.is_root(): if org.is_root():
return return
# add user to org # add user to org
for obj in objs: for user in users:
org.add_member(obj) org.add_member(user)
# add user to group # add user to group
with tmp_to_org(org): with tmp_to_org(org):
for group_name, users in group_users_mapper.items(): for group_name, users in group_users_mapper.items():
@ -430,8 +437,6 @@ class LDAPImportUtil(object):
name=group_name, defaults={'name': group_name} name=group_name, defaults={'name': group_name}
) )
group.users.add(*users) group.users.add(*users)
logger.info('End perform import ldap users')
return errors
class LDAPTestUtil(object): class LDAPTestUtil(object):