mirror of https://github.com/jumpserver/jumpserver
perf: ldap 能多组织同步用户 (#10543)
Co-authored-by: feng <1304903146@qq.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>pull/10556/head
parent
fa21c83db3
commit
a6366a2dd4
|
@ -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,
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
]
|
|
@ -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(
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue