views.py 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  1. #!/usr/bin/env python
  2. # Licensed to Cloudera, Inc. under one
  3. # or more contributor license agreements. See the NOTICE file
  4. # distributed with this work for additional information
  5. # regarding copyright ownership. Cloudera, Inc. licenses this file
  6. # to you under the Apache License, Version 2.0 (the
  7. # "License"); you may not use this file except in compliance
  8. # with the License. You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. import pwd
  18. import grp
  19. import logging
  20. import threading
  21. import subprocess
  22. import json
  23. from axes.decorators import FAILURE_LIMIT, LOCK_OUT_AT_FAILURE
  24. from axes.models import AccessAttempt
  25. from axes.utils import reset
  26. import ldap
  27. import ldap_access
  28. from ldap_access import LdapBindException, LdapSearchException
  29. from django.contrib.auth.models import User, Group
  30. from django.core.urlresolvers import reverse
  31. from django.forms import ValidationError
  32. from django.forms.util import ErrorList
  33. from django.http import HttpResponse
  34. from django.shortcuts import redirect
  35. from django.utils.encoding import smart_str
  36. from django.utils.translation import get_language, ugettext as _
  37. import desktop.conf
  38. from desktop.conf import LDAP
  39. from desktop.lib.django_util import JsonResponse, render
  40. from desktop.lib.exceptions_renderable import PopupException
  41. from hadoop.fs.exceptions import WebHdfsException
  42. from useradmin.models import HuePermission, UserProfile, LdapGroup
  43. from useradmin.models import get_profile, get_default_user_group
  44. from useradmin.forms import SyncLdapUsersGroupsForm, AddLdapGroupsForm, AddLdapUsersForm,\
  45. PermissionsEditForm, GroupEditForm, SuperUserChangeForm, UserChangeForm, validate_username, validate_first_name, \
  46. validate_last_name, PasswordChangeForm
  47. LOG = logging.getLogger(__name__)
  48. __users_lock = threading.Lock()
  49. __groups_lock = threading.Lock()
  50. def list_users(request):
  51. is_ldap_setup = bool(LDAP.LDAP_SERVERS.get()) or LDAP.LDAP_URL.get() is not None
  52. return render("list_users.mako", request, {
  53. 'users': User.objects.all(),
  54. 'users_json': json.dumps(list(User.objects.values_list('id', flat=True))),
  55. 'request': request,
  56. 'is_embeddable': request.GET.get('is_embeddable', False),
  57. 'is_ldap_setup': is_ldap_setup
  58. })
  59. def list_groups(request):
  60. is_ldap_setup = bool(LDAP.LDAP_SERVERS.get()) or LDAP.LDAP_URL.get() is not None
  61. return render("list_groups.mako", request, {
  62. 'groups': Group.objects.all(),
  63. 'groups_json': json.dumps(list(Group.objects.values_list('name', flat=True))),
  64. 'is_embeddable': request.GET.get('is_embeddable', False),
  65. 'is_ldap_setup': is_ldap_setup
  66. })
  67. def list_permissions(request):
  68. return render("list_permissions.mako", request, {
  69. 'permissions': HuePermission.objects.all(),
  70. 'is_embeddable': request.GET.get('is_embeddable', False)
  71. })
  72. def list_configurations(request):
  73. return render("list_configurations.mako", request, {
  74. 'is_embeddable': request.GET.get('is_embeddable', False)
  75. })
  76. def list_for_autocomplete(request):
  77. if request.ajax:
  78. extended_user_object = request.GET.get('extend_user') == 'true'
  79. if request.user.is_superuser:
  80. users = User.objects.all().order_by('username')
  81. groups = Group.objects.all().order_by('name')
  82. if request.GET.get('only_mygroups'):
  83. groups = request.user.groups.all()
  84. else:
  85. usergroups = request.user.groups.all()
  86. users = User.objects.filter(groups__in=usergroups).order_by('username')
  87. groups = usergroups.order_by('name')
  88. if not request.GET.get('include_myself'):
  89. users = users.exclude(pk=request.user.pk)
  90. users = users[:2000]
  91. groups = groups[:2000]
  92. response = {
  93. 'users': massage_users_for_json(users, extended_user_object),
  94. 'groups': massage_groups_for_json(groups)
  95. }
  96. return JsonResponse(response)
  97. return HttpResponse("")
  98. def massage_users_for_json(users, extended=False):
  99. simple_users = []
  100. for user in users:
  101. appendable = {
  102. 'id': user.id,
  103. 'username': user.username,
  104. 'first_name': user.first_name,
  105. 'last_name': user.last_name,
  106. 'email': user.email,
  107. 'last_login': user.last_login,
  108. 'editURL': reverse('useradmin.views.edit_user', kwargs={'username': user.username})
  109. }
  110. if extended:
  111. appendable['groups'] = massage_groups_for_json(user.groups.all())
  112. simple_users.append(appendable)
  113. return simple_users
  114. def massage_groups_for_json(groups):
  115. simple_groups = []
  116. for group in groups:
  117. simple_groups.append({
  118. 'id': group.id,
  119. 'name': group.name
  120. })
  121. return simple_groups
  122. def is_user_locked_out(username):
  123. attempts = AccessAttempt.objects.filter(username=username)
  124. return any(attempt.failures_since_start >= FAILURE_LIMIT and LOCK_OUT_AT_FAILURE for attempt in attempts)
  125. def delete_user(request):
  126. if not request.user.is_superuser:
  127. request.audit = {
  128. 'operation': 'DELETE_USER',
  129. 'operationText': _get_failed_operation_text(request.user.username, 'DELETE_USER'),
  130. 'allowed': False
  131. }
  132. raise PopupException(_("You must be a superuser to delete users."), error_code=401)
  133. if request.method != 'POST':
  134. raise PopupException(_('A POST request is required.'))
  135. ids = request.POST.getlist('user_ids')
  136. global __users_lock
  137. __users_lock.acquire()
  138. try:
  139. if str(request.user.id) in ids:
  140. raise PopupException(_("You cannot remove yourself."), error_code=401)
  141. usernames = list(User.objects.filter(id__in=ids).values_list('username', flat=True))
  142. UserProfile.objects.filter(user__id__in=ids).delete()
  143. User.objects.filter(id__in=ids).delete()
  144. request.audit = {
  145. 'operation': 'DELETE_USER',
  146. 'operationText': 'Deleted User(s): %s' % ', '.join(usernames)
  147. }
  148. finally:
  149. __users_lock.release()
  150. request.info(_('The users were deleted.'))
  151. return redirect(reverse(list_users))
  152. def delete_group(request):
  153. if not request.user.is_superuser:
  154. request.audit = {
  155. 'operation': 'DELETE_GROUP',
  156. 'operationText': _get_failed_operation_text(request.user.username, 'DELETE_GROUP'),
  157. 'allowed': False
  158. }
  159. raise PopupException(_("You must be a superuser to delete groups."), error_code=401)
  160. if request.method == 'POST':
  161. try:
  162. group_names = request.POST.getlist('group_names')
  163. # Get the default group before getting the group, because we may be
  164. # trying to delete the default group, and it may not have been created yet.
  165. default_group = get_default_user_group()
  166. if default_group is not None and default_group.name in group_names:
  167. raise PopupException(_("The default user group may not be deleted."), error_code=401)
  168. Group.objects.filter(name__in=group_names).delete()
  169. request.info(_('The groups were deleted.'))
  170. request.audit = {
  171. 'operation': 'DELETE_GROUP',
  172. 'operationText': 'Deleted Group(s): %s' % ', '.join(group_names)
  173. }
  174. return redirect(reverse(list_groups))
  175. except Group.DoesNotExist:
  176. raise PopupException(_("Group not found."), error_code=404)
  177. else:
  178. return render("delete_group.mako", request, {'path': request.path})
  179. def require_change_password(self):
  180. """ Return true if user has never logged in before. """
  181. if 'desktop.auth.backend.AllowFirstUserDjangoBackend' in desktop.conf.AUTH.BACKEND.get() and \
  182. self.first_login and desktop.conf.AUTH.CHANGE_DEFAULT_PASSWORD.get():
  183. return True
  184. def edit_user(request, username=None):
  185. """
  186. edit_user(request, username = None) -> reply
  187. @type request: HttpRequest
  188. @param request: The request object
  189. @type username: string
  190. @param username: Default to None, when creating a new user
  191. """
  192. if request.user.username != username and not request.user.is_superuser:
  193. request.audit = {'allowed': False}
  194. if username is not None:
  195. request.audit['operation'] = 'EDIT_USER'
  196. request.audit['operationText'] = _get_failed_operation_text(request.user.username, 'EDIT_USER')
  197. else:
  198. request.audit['operation'] = 'CREATE_USER'
  199. request.audit['operationText'] = _get_failed_operation_text(request.user.username, 'CREATE_USER')
  200. raise PopupException(_("You must be a superuser to add or edit another user."), error_code=401)
  201. userprofile = get_profile(request.user)
  202. if username is not None:
  203. instance = User.objects.get(username=username)
  204. else:
  205. instance = None
  206. if require_change_password(userprofile):
  207. form_class = PasswordChangeForm
  208. elif request.user.is_superuser:
  209. form_class = SuperUserChangeForm
  210. else:
  211. form_class = UserChangeForm
  212. if request.method == 'POST':
  213. form = form_class(request.POST, instance=instance)
  214. if request.user.is_superuser and request.user.username != username:
  215. form.fields.pop("password_old")
  216. if form.is_valid(): # All validation rules pass
  217. if instance is None:
  218. instance = form.save()
  219. get_profile(instance)
  220. else:
  221. if username != form.instance.username:
  222. raise PopupException(_("You cannot change a username."), error_code=401)
  223. if request.user.username == username and not form.instance.is_active:
  224. raise PopupException(_("You cannot make yourself inactive."), error_code=401)
  225. global __users_lock
  226. __users_lock.acquire()
  227. try:
  228. # form.instance (and instance) now carry the new data
  229. orig = User.objects.get(username=username)
  230. if orig.is_superuser:
  231. if not form.instance.is_superuser or not form.instance.is_active:
  232. _check_remove_last_super(orig)
  233. else:
  234. if form.instance.is_superuser and not request.user.is_superuser:
  235. raise PopupException(_("You cannot make yourself a superuser."), error_code=401)
  236. # All ok
  237. form.save()
  238. request.info(_('User information updated'))
  239. # Unlock account if selected
  240. if form.cleaned_data.get('unlock_account'):
  241. if not request.user.is_superuser:
  242. raise PopupException(_('You must be a superuser to reset users.'), error_code=401)
  243. try:
  244. reset(username=username)
  245. request.info(_('Successfully unlocked account for user: %s') % username)
  246. except Exception, e:
  247. raise PopupException(_('Failed to reset login attempts for %s: %s') % (username, str(e)))
  248. finally:
  249. __users_lock.release()
  250. # Ensure home directory is created, if necessary.
  251. if form.cleaned_data.get('ensure_home_directory'):
  252. try:
  253. ensure_home_directory(request.fs, instance)
  254. except (IOError, WebHdfsException), e:
  255. request.error(_('Cannot make home directory for user %s.') % instance.username)
  256. # Change langugage preference, if necessary
  257. if form.cleaned_data.get('language') and form.cleaned_data.get('language') != get_language():
  258. request.session['django_language'] = form.cleaned_data.get('language')
  259. # Audit log
  260. if username is not None:
  261. request.audit = {
  262. 'operation': 'EDIT_USER',
  263. 'operationText': 'Edited User with username: %s' % username
  264. }
  265. else:
  266. request.audit = {
  267. 'operation': 'CREATE_USER',
  268. 'operationText': 'Created User with username: %s' % instance.username
  269. }
  270. if require_change_password(userprofile):
  271. userprofile.first_login = False
  272. userprofile.save()
  273. if request.user.is_superuser:
  274. return redirect(reverse('about:index'))
  275. else:
  276. return redirect(reverse('desktop.views.home'))
  277. elif request.user.is_superuser:
  278. return redirect(reverse(list_users))
  279. else:
  280. return redirect(reverse(edit_user, kwargs={'username': username}))
  281. else:
  282. # Initialize form values
  283. default_user_group = get_default_user_group()
  284. initial = {
  285. 'language': request.session.get('django_language', get_language()),
  286. 'ensure_home_directory': instance is None,
  287. 'groups': default_user_group and [default_user_group] or []
  288. }
  289. form = form_class(instance=instance, initial=initial)
  290. if request.user.is_superuser and request.user.username != username:
  291. form.fields.pop("password_old")
  292. if require_change_password(userprofile):
  293. return render('change_password.mako', request, {
  294. 'form': form,
  295. 'username': username,
  296. 'is_embeddable': request.GET.get('is_embeddable', False),
  297. })
  298. else:
  299. return render('edit_user.mako', request, {
  300. 'form': form,
  301. 'username': username,
  302. 'is_embeddable': request.GET.get('is_embeddable', False),
  303. })
  304. def view_user(request, username):
  305. instance = User.objects.get(username=username)
  306. return render('view_user.mako', request, {'instance': instance})
  307. def edit_group(request, name=None):
  308. """
  309. edit_group(request, name = None) -> reply
  310. @type request: HttpRequest
  311. @param request: The request object
  312. @type name: string
  313. @param name: Default to None, when creating a new group
  314. Only superusers may create a group
  315. """
  316. if not request.user.is_superuser:
  317. request.audit = {'allowed': False}
  318. if name is not None:
  319. request.audit['operation'] = 'EDIT_GROUP'
  320. request.audit['operationText'] = _get_failed_operation_text(request.user.username, 'EDIT_GROUP')
  321. else:
  322. request.audit['operation'] = 'CREATE_GROUP'
  323. request.audit['operationText'] = _get_failed_operation_text(request.user.username, 'CREATE_GROUP')
  324. raise PopupException(_("You must be a superuser to add or edit a group."), error_code=401)
  325. if name is not None:
  326. instance = Group.objects.get(name=name)
  327. else:
  328. instance = None
  329. if request.method == 'POST':
  330. form = GroupEditForm(request.POST, instance=instance)
  331. if form.is_valid():
  332. form.save()
  333. request.info(_('Group information updated'))
  334. # Audit log
  335. if name is not None:
  336. usernames = instance.user_set.all().values_list('username', flat=True)
  337. request.audit = {
  338. 'operation': 'EDIT_GROUP',
  339. 'operationText': 'Edited Group: %s, with member(s): %s' % (name, ', '.join(usernames))
  340. }
  341. else:
  342. user_ids = request.POST.getlist('members', [])
  343. usernames = User.objects.filter(pk__in=user_ids).values_list('username', flat=True)
  344. request.audit = {
  345. 'operation': 'CREATE_GROUP',
  346. 'operationText': 'Created Group: %s, with member(s): %s' % (request.POST.get('name', ''), ', '.join(usernames))
  347. }
  348. return redirect(reverse(list_groups))
  349. else:
  350. form = GroupEditForm(instance=instance)
  351. return render('edit_group.mako', request, dict(form=form, action=request.path, name=name))
  352. def edit_permission(request, app=None, priv=None):
  353. """
  354. edit_permission(request, app = None, priv = None) -> reply
  355. @type request: HttpRequest
  356. @param request: The request object
  357. @type app: string
  358. @param app: Default to None, specifies the app of the privilege
  359. @type priv: string
  360. @param priv Default to None, the action of the privilege
  361. Only superusers may modify permissions
  362. """
  363. if not request.user.is_superuser:
  364. request.audit = {
  365. 'operation': 'EDIT_PERMISSION',
  366. 'operationText': _get_failed_operation_text(request.user.username, 'EDIT_PERMISSION'),
  367. 'allowed': False
  368. }
  369. raise PopupException(_("You must be a superuser to change permissions."), error_code=401)
  370. instance = HuePermission.objects.get(app=app, action=priv)
  371. if request.method == 'POST':
  372. form = PermissionsEditForm(request.POST, instance=instance)
  373. if form.is_valid():
  374. form.save()
  375. request.info(_('Permission information updated'))
  376. request.audit = {
  377. 'operation': 'EDIT_PERMISSION',
  378. 'operationText': 'Successfully edited permissions: %(app)s/%(priv)s' % {'app': app, 'priv': priv}
  379. }
  380. return redirect(reverse(list_permissions))
  381. else:
  382. form = PermissionsEditForm(instance=instance)
  383. return render('edit_permissions.mako', request, dict(form=form, action=request.path, app=app, priv=priv))
  384. def add_ldap_users(request):
  385. """
  386. add_ldap_users(request) -> reply
  387. Handler for importing LDAP users into the Hue database.
  388. If a user has been previously imported, this will sync their user information.
  389. If the LDAP request failed, the error message is generic right now.
  390. """
  391. if not request.user.is_superuser:
  392. request.audit = {
  393. 'operation': 'ADD_LDAP_USERS',
  394. 'operationText': _get_failed_operation_text(request.user.username, 'ADD_LDAP_USERS'),
  395. 'allowed': False,
  396. }
  397. raise PopupException(_("You must be a superuser to add another user."), error_code=401)
  398. if request.method == 'POST':
  399. form = AddLdapUsersForm(request.POST)
  400. if form.is_valid():
  401. username_pattern = form.cleaned_data['username_pattern']
  402. import_by_dn = form.cleaned_data['dn']
  403. server = form.cleaned_data.get('server')
  404. try:
  405. failed_ldap_users = []
  406. connection = ldap_access.get_connection_from_server(server)
  407. users = import_ldap_users(connection, username_pattern, False, import_by_dn, failed_users=failed_ldap_users)
  408. except (ldap.LDAPError, LdapBindException), e:
  409. LOG.error("LDAP Exception: %s" % e)
  410. raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
  411. except ValidationError, e:
  412. raise PopupException(_('There was a problem with some of the LDAP information'), detail=str(e))
  413. if users and form.cleaned_data['ensure_home_directory']:
  414. for user in users:
  415. try:
  416. ensure_home_directory(request.fs, user)
  417. except (IOError, WebHdfsException), e:
  418. request.error(_("Cannot make home directory for user %s.") % user.username)
  419. if not users:
  420. errors = form._errors.setdefault('username_pattern', ErrorList())
  421. errors.append(_('Could not get LDAP details for users in pattern %s.') % username_pattern)
  422. else:
  423. request.audit = {
  424. 'operation': 'ADD_LDAP_USERS',
  425. 'operationText': 'Added/Synced LDAP username(s): %s' % username_pattern
  426. }
  427. if failed_ldap_users:
  428. unique_users = set(failed_ldap_users)
  429. request.warn(_('Failed to import following users: %s') % ', '.join(unique_users))
  430. return redirect(reverse(list_users))
  431. else:
  432. form = AddLdapUsersForm()
  433. return render('add_ldap_users.mako', request, dict(form=form))
  434. def add_ldap_groups(request):
  435. """
  436. add_ldap_groups(request) -> reply
  437. Handler for importing LDAP groups into the Hue database.
  438. If a group has been previously imported, this will sync membership within the
  439. group with the LDAP server. If --import-members is specified, it will import
  440. all unimported users.
  441. """
  442. if not request.user.is_superuser:
  443. request.audit = {
  444. 'operation': 'ADD_LDAP_GROUPS',
  445. 'operationText': _get_failed_operation_text(request.user.username, 'ADD_LDAP_GROUPS'),
  446. 'allowed': False,
  447. }
  448. raise PopupException(_("You must be a superuser to add another group."), error_code=401)
  449. if request.method == 'POST':
  450. form = AddLdapGroupsForm(request.POST)
  451. if form.is_valid():
  452. groupname_pattern = form.cleaned_data['groupname_pattern']
  453. import_by_dn = form.cleaned_data['dn']
  454. import_members = form.cleaned_data['import_members']
  455. import_members_recursive = form.cleaned_data['import_members_recursive']
  456. is_ensuring_home_directories = form.cleaned_data['ensure_home_directories']
  457. server = form.cleaned_data.get('server')
  458. try:
  459. failed_ldap_users = []
  460. connection = ldap_access.get_connection_from_server(server)
  461. groups = import_ldap_groups(connection, groupname_pattern, import_members=import_members,
  462. import_members_recursive=import_members_recursive, sync_users=True,
  463. import_by_dn=import_by_dn, failed_users=failed_ldap_users)
  464. except (ldap.LDAPError, LdapBindException), e:
  465. LOG.error(_("LDAP Exception: %s") % e)
  466. raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
  467. except ValidationError, e:
  468. raise PopupException(_('There was a problem with some of the LDAP information'), detail=str(e))
  469. unique_users = set()
  470. if is_ensuring_home_directories and groups:
  471. for group in groups:
  472. for user in group.user_set.all():
  473. unique_users.add(user)
  474. for user in unique_users:
  475. try:
  476. ensure_home_directory(request.fs, user)
  477. except (IOError, WebHdfsException), e:
  478. raise PopupException(_("Exception creating home directory for LDAP user %s in group %s.") % (user, group), detail=e)
  479. if groups:
  480. request.audit = {
  481. 'operation': 'ADD_LDAP_GROUPS',
  482. 'operationText': 'Added LDAP Group(s): %s' % groupname_pattern
  483. }
  484. if failed_ldap_users:
  485. unique_users = set(failed_ldap_users)
  486. request.warn(_('Failed to import following users: %s') % ', '.join(unique_users))
  487. return redirect(reverse(list_groups))
  488. else:
  489. errors = form._errors.setdefault('groupname_pattern', ErrorList())
  490. errors.append(_('Could not get LDAP details for groups in pattern %s') % groupname_pattern)
  491. else:
  492. form = AddLdapGroupsForm()
  493. return render('edit_group.mako', request, dict(form=form, action=request.path, ldap=True))
  494. def sync_ldap_users_groups(request):
  495. """
  496. Handler for syncing the Hue database with LDAP users and groups.
  497. This will not import any users or groups that don't already exist in Hue. All
  498. user information and group memberships will be updated based on the LDAP
  499. server's current state.
  500. """
  501. if not request.user.is_superuser:
  502. request.audit = {
  503. 'operation': 'SYNC_LDAP_USERS_GROUPS',
  504. 'operationText': _get_failed_operation_text(request.user.username, 'SYNC_LDAP_USERS_GROUPS'),
  505. 'allowed': False
  506. }
  507. raise PopupException(_("You must be a superuser to sync the LDAP users/groups."), error_code=401)
  508. if request.method == 'POST':
  509. form = SyncLdapUsersGroupsForm(request.POST)
  510. if form.is_valid():
  511. is_ensuring_home_directory = form.cleaned_data['ensure_home_directory']
  512. server = form.cleaned_data.get('server')
  513. connection = ldap_access.get_connection_from_server(server)
  514. failed_ldap_users = []
  515. sync_ldap_users_and_groups(connection, is_ensuring_home_directory, request.fs,
  516. failed_users=failed_ldap_users)
  517. request.audit = {
  518. 'operation': 'SYNC_LDAP_USERS_GROUPS',
  519. 'operationText': 'Successfully synced LDAP users/groups'
  520. }
  521. if failed_ldap_users:
  522. unique_users = set(failed_ldap_users)
  523. request.warn(_('Failed to import following users: %s') % ', '.join(unique_users))
  524. return redirect(reverse(list_users))
  525. else:
  526. form = SyncLdapUsersGroupsForm()
  527. return render("sync_ldap_users_groups.mako", request, dict(path=request.path, form=form))
  528. def sync_ldap_users_and_groups(connection, is_ensuring_home_directory=False, fs=None, failed_users=None):
  529. try:
  530. users = sync_ldap_users(connection, failed_users=failed_users)
  531. groups = sync_ldap_groups(connection, failed_users=failed_users)
  532. except (ldap.LDAPError, LdapBindException), e:
  533. LOG.error("LDAP Exception: %s" % e)
  534. raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
  535. # Create home dirs for every user sync'd
  536. if is_ensuring_home_directory:
  537. for user in users:
  538. try:
  539. ensure_home_directory(fs, user)
  540. except (IOError, WebHdfsException), e:
  541. raise PopupException(_("The import may not be complete, sync again."), detail=e)
  542. def import_ldap_users(connection, user_pattern, sync_groups, import_by_dn, server=None, failed_users=None):
  543. return _import_ldap_users(connection, user_pattern, sync_groups=sync_groups, import_by_dn=import_by_dn, server=server,
  544. failed_users=failed_users)
  545. def import_ldap_groups(connection, group_pattern, import_members, import_members_recursive, sync_users, import_by_dn,
  546. failed_users=None):
  547. return _import_ldap_groups(connection, group_pattern, import_members, import_members_recursive, sync_users,
  548. import_by_dn, failed_users=failed_users)
  549. def sync_ldap_users(connection, failed_users=None):
  550. """
  551. Syncs LDAP user information. This will not import new
  552. users from LDAP. It is also not possible to import both a user and a
  553. group at the same time. Each must be a separate operation. If neither a user,
  554. nor a group is provided, all users and groups will be synced.
  555. """
  556. users = User.objects.filter(userprofile__creation_method=str(UserProfile.CreationMethod.EXTERNAL)).all()
  557. for user in users:
  558. _import_ldap_users(connection, user.username, failed_users=failed_users)
  559. return users
  560. def sync_ldap_groups(connection, failed_users=None):
  561. """
  562. Syncs LDAP group memberships. This will not import new
  563. groups from LDAP. It is also not possible to import both a user and a
  564. group at the same time. Each must be a separate operation. If neither a user,
  565. nor a group is provided, all users and groups will be synced.
  566. """
  567. groups = Group.objects.filter(group__in=LdapGroup.objects.all())
  568. for group in groups:
  569. _import_ldap_groups(connection, group.name, failed_users=failed_users)
  570. return groups
  571. def ensure_home_directory(fs, user):
  572. """
  573. Adds a users home directory if it doesn't already exist.
  574. Throws IOError, WebHdfsException.
  575. """
  576. userprofile = get_profile(user)
  577. if userprofile is not None and userprofile.home_directory:
  578. fs.do_as_user(user.username, fs.create_home_dir, userprofile.home_directory)
  579. else:
  580. LOG.warn("Not creating home directory of %s as his profile is empty" % user)
  581. def sync_unix_users_and_groups(min_uid, max_uid, min_gid, max_gid, check_shell):
  582. """
  583. Syncs the Hue database with the underlying Unix system, by importing users and
  584. groups from 'getent passwd' and 'getent groups'. This should also pull in
  585. users who are accessible via NSS.
  586. """
  587. global __users_lock, __groups_lock
  588. hadoop_groups = dict((group.gr_name, group) for group in grp.getgrall() \
  589. if (group.gr_gid >= min_gid and group.gr_gid < max_gid) or group.gr_name == 'hadoop')
  590. user_groups = dict()
  591. __users_lock.acquire()
  592. __groups_lock.acquire()
  593. # Import groups
  594. for name, group in hadoop_groups.iteritems():
  595. try:
  596. if len(group.gr_mem) != 0:
  597. hue_group = Group.objects.get(name=name)
  598. except Group.DoesNotExist:
  599. hue_group = Group(name=name)
  600. hue_group.save()
  601. LOG.info("Created group %s" % (hue_group.name,))
  602. # Build a map of user to groups that the user is a member of
  603. members = group.gr_mem
  604. for member in members:
  605. if member not in user_groups:
  606. user_groups[member] = [ hue_group ]
  607. else:
  608. user_groups[member].append(hue_group)
  609. # Now let's import the users
  610. hadoop_users = dict((user.pw_name, user) for user in pwd.getpwall() \
  611. if (user.pw_uid >= min_uid and user.pw_uid < max_uid) or user.pw_name in grp.getgrnam('hadoop').gr_mem)
  612. for username, user in hadoop_users.iteritems():
  613. try:
  614. if check_shell:
  615. pw_shell = user.pw_shell
  616. if subprocess.call([pw_shell, "-c", "echo"], stdout=subprocess.PIPE) != 0:
  617. continue
  618. hue_user = User.objects.get(username=username)
  619. except User.DoesNotExist:
  620. hue_user = User(username=username, password='!', is_active=True, is_superuser=False)
  621. hue_user.set_unusable_password()
  622. # We have to do a save here, because the user needs to exist before we can
  623. # access the associated list of groups
  624. hue_user.save()
  625. if username not in user_groups:
  626. hue_user.groups = []
  627. else:
  628. # Here's where that user to group map we built comes in handy
  629. hue_user.groups = user_groups[username]
  630. hue_user.save()
  631. LOG.info(_("Synced user %s from Unix") % hue_user.username)
  632. __users_lock.release()
  633. __groups_lock.release()
  634. def _check_remove_last_super(user_obj):
  635. """Raise an error if we're removing the last superuser"""
  636. if not user_obj.is_superuser:
  637. return
  638. # Is there any other active superuser left?
  639. all_active_su = User.objects.filter(is_superuser__exact = True,
  640. is_active__exact = True)
  641. num_active_su = all_active_su.count()
  642. if num_active_su < 1:
  643. raise PopupException(_("No active superuser configured."))
  644. if num_active_su == 1:
  645. raise PopupException(_("You cannot remove the last active superuser from the configuration."), error_code=401)
  646. def _import_ldap_users(connection, username_pattern, sync_groups=False, import_by_dn=False, server=None, failed_users=None):
  647. """
  648. Import a user from LDAP. If import_by_dn is true, this will import the user by
  649. the distinguished name, rather than the configured username attribute.
  650. """
  651. user_info = None
  652. try:
  653. user_info = connection.find_users(username_pattern, find_by_dn=import_by_dn)
  654. except LdapSearchException, e:
  655. LOG.warn("Failed to find LDAP user: %s" % e)
  656. if not user_info:
  657. LOG.warn("Could not get LDAP details for users with pattern %s" % username_pattern)
  658. return None
  659. return _import_ldap_users_info(connection, user_info, sync_groups, import_by_dn, server, failed_users=failed_users)
  660. def _import_ldap_users_info(connection, user_info, sync_groups=False, import_by_dn=False, server=None, failed_users=None):
  661. """
  662. Import user_info found through ldap_access.find_users.
  663. """
  664. imported_users = []
  665. for ldap_info in user_info:
  666. # Extra validation in case import by DN and username has spaces or colons
  667. try:
  668. validate_username(ldap_info['username'])
  669. user, created = ldap_access.get_or_create_ldap_user(username=ldap_info['username'])
  670. profile = get_profile(user)
  671. if not created and profile.creation_method == str(UserProfile.CreationMethod.HUE):
  672. # This is a Hue user, and shouldn't be overwritten
  673. LOG.warn(_('There was a naming conflict while importing user %(username)s') % {
  674. 'username': ldap_info['username']
  675. })
  676. return None
  677. default_group = get_default_user_group()
  678. if created and default_group is not None:
  679. user.groups.add(default_group)
  680. if 'first' in ldap_info:
  681. validate_first_name(ldap_info['first'])
  682. user.first_name = ldap_info['first']
  683. if 'last' in ldap_info:
  684. validate_last_name(ldap_info['last'])
  685. user.last_name = ldap_info['last']
  686. if 'email' in ldap_info:
  687. user.email = ldap_info['email']
  688. profile.creation_method = UserProfile.CreationMethod.EXTERNAL
  689. profile.save()
  690. user.save()
  691. imported_users.append(user)
  692. # sync groups
  693. if sync_groups:
  694. old_groups = set(user.groups.all())
  695. new_groups = set()
  696. current_ldap_groups = set()
  697. ldap_config = desktop.conf.LDAP.LDAP_SERVERS.get()[server] if server else desktop.conf.LDAP
  698. group_member_attr = ldap_config.GROUPS.GROUP_MEMBER_ATTR.get()
  699. group_filter = ldap_config.GROUPS.GROUP_FILTER.get()
  700. # Search for groups based on group_member_attr=username and group_member_attr=dn
  701. # covers AD, Standard Ldap and posixAcount/posixGroup
  702. if not group_filter.startswith('('):
  703. group_filter = '(' + group_filter + ')'
  704. # Sanitizing the DN before using in a Search filter
  705. sanitized_dn = ldap.filter.escape_filter_chars(ldap_info['dn']).replace(r'\2a', r'*')
  706. sanitized_dn = sanitized_dn.replace(r'\5c,', r'\5c\2c')
  707. find_groups_filter = "(&" + group_filter + "(|(" + group_member_attr + "=" + ldap_info['username'] + ")(" + \
  708. group_member_attr + "=" + sanitized_dn + ")))"
  709. group_ldap_info = connection.find_groups("*", group_filter=find_groups_filter)
  710. for group_info in group_ldap_info:
  711. if Group.objects.filter(name=group_info['name']).exists():
  712. # Add only if user isn't part of group.
  713. current_ldap_groups.add(Group.objects.get(name=group_info['name']))
  714. if not user.groups.filter(name=group_info['name']).exists():
  715. groups = import_ldap_groups(connection, group_info['dn'], import_members=False, import_members_recursive=False,
  716. sync_users=True, import_by_dn=True, failed_users=failed_users)
  717. if groups:
  718. new_groups.update(groups)
  719. # Remove out of date groups
  720. remove_groups = old_groups - current_ldap_groups
  721. remove_ldap_groups = LdapGroup.objects.filter(group__in=remove_groups)
  722. remove_groups_filtered = [ldapgroup.group for ldapgroup in remove_ldap_groups]
  723. for group in remove_groups_filtered:
  724. user.groups.remove(group)
  725. user.groups.add(*new_groups)
  726. Group.objects.filter(group__in=remove_groups_filtered).delete()
  727. except (ValidationError, LdapSearchException) as e:
  728. if failed_users is None:
  729. failed_users = []
  730. failed_users.append(ldap_info['username'])
  731. LOG.warn('Could not import %s: %s' % (ldap_info['username'], e.message))
  732. return imported_users
  733. def _import_ldap_members(connection, group, ldap_info, count=0, max_count=1, failed_users=None):
  734. if count >= max_count:
  735. return None
  736. # Find all users and groups of group.
  737. users_info, groups_info = [], []
  738. try:
  739. users_info = connection.find_users_of_group(ldap_info['dn'])
  740. except LdapSearchException, e:
  741. LOG.warn("Failed to find LDAP users of group: %s" % e)
  742. try:
  743. groups_info = connection.find_groups_of_group(ldap_info['dn'])
  744. except LdapSearchException, e:
  745. LOG.warn("Failed to find LDAP groups of group: %s" % e)
  746. posix_members = ldap_info['posix_members']
  747. for user_info in users_info:
  748. LOG.debug("Importing user %s into group %s" % (smart_str(user_info['dn']), smart_str(group.name)))
  749. users = _import_ldap_users(connection, smart_str(user_info['dn']), import_by_dn=True, failed_users=None)
  750. group.user_set.add(*users)
  751. for group_info in groups_info:
  752. LOG.debug("Importing group %s" % smart_str(group_info['dn']))
  753. groups = _import_ldap_groups(connection, smart_str(group_info['dn']), import_by_dn=True, failed_users=None)
  754. # Must find all members of subgroups
  755. if len(groups) > 1:
  756. LOG.warn('Found multiple groups for member %s.' % smart_str(group_info['dn']))
  757. else:
  758. for group in groups:
  759. _import_ldap_members(connection, group, group_info, count+1, max_count, failed_users=failed_users)
  760. for posix_member in posix_members:
  761. LOG.debug("Importing posix user %s into group %s" % (smart_str(posix_member), smart_str(group.name)))
  762. user_info = None
  763. try:
  764. user_info = connection.find_users(posix_member, search_attr='uid', user_name_attr=desktop.conf.LDAP.USERS.USER_NAME_ATTR.get(), find_by_dn=False)
  765. except LdapSearchException, e:
  766. LOG.warn("Failed to find LDAP users: %s" % e)
  767. if user_info:
  768. users = _import_ldap_users_info(connection, user_info, failed_users=failed_users)
  769. if users:
  770. LOG.debug("Adding member %s represented as users (should be a single user) %s to group %s" % (str(posix_member), str(users), str(group.name)))
  771. group.user_set.add(*users)
  772. def _sync_ldap_members(connection, group, ldap_info, count=0, max_count=1, failed_users=None):
  773. if count >= max_count:
  774. return None
  775. # Find all users and groups of group.
  776. users_info, groups_info = [], []
  777. try:
  778. users_info = connection.find_users_of_group(ldap_info['dn'])
  779. except LdapSearchException, e:
  780. LOG.warn("Failed to find LDAP users of group: %s" % e)
  781. try:
  782. groups_info = connection.find_groups_of_group(ldap_info['dn'])
  783. except LdapSearchException, e:
  784. LOG.warn("Failed to find LDAP groups of group: %s" % e)
  785. posix_members = ldap_info['posix_members']
  786. for user_info in users_info:
  787. LOG.debug("Synchronizing user %s with group %s" % (smart_str(user_info['dn']), smart_str(group.name)))
  788. try:
  789. user = ldap_access.get_ldap_user(username=user_info['username'])
  790. group.user_set.add(user)
  791. except User.DoesNotExist:
  792. LOG.warn("Synchronizing user %s with group %s failed. User does not exist." % (smart_str(user_info['dn']), smart_str(group.name)))
  793. for group_info in groups_info:
  794. LOG.debug("Synchronizing group %s" % smart_str(group_info['dn']))
  795. try:
  796. group = Group.objects.get(name=group_info['name'])
  797. _sync_ldap_members(connection, group, group_info, count+1, max_count, failed_users=failed_users)
  798. except Group.DoesNotExist:
  799. LOG.warn("Synchronizing group %s failed. Group does not exist." % smart_str(group.name))
  800. for posix_member in posix_members:
  801. LOG.debug("Synchronizing posix user %s with group %s" % (smart_str(posix_member), smart_str(group.name)))
  802. users_info = []
  803. try:
  804. users_info = connection.find_users(posix_member, search_attr='uid', user_name_attr=desktop.conf.LDAP.USERS.USER_NAME_ATTR.get(), find_by_dn=False)
  805. except LdapSearchException, e:
  806. LOG.warn("Failed to find LDAP users: %s" % e)
  807. for user_info in users_info:
  808. try:
  809. user = ldap_access.get_ldap_user(username=user_info['username'])
  810. group.user_set.add(user)
  811. except User.DoesNotExist:
  812. LOG.warn("Synchronizing posix user %s with group %s failed. User does not exist." % (smart_str(posix_member), smart_str(group.name)))
  813. def _import_ldap_nested_groups(connection, groupname_pattern, import_members=False, recursive_import_members=False,
  814. sync_users=True, import_by_dn=False, failed_users=None):
  815. """
  816. Import a group from LDAP. If import_members is true, this will also import any
  817. LDAP users that exist within the group. This will use nested groups logic.
  818. A nested group is a group that is a member of another group.
  819. e.g. CN=subtest,OU=groups,DC=exampe,DC=COM is a member of CN=test,OU=groups,DC=exampe,DC=COM
  820. and they both of the object class "groupOfNames" (or some other object class for groups).
  821. """
  822. if import_by_dn:
  823. scope = ldap.SCOPE_BASE
  824. else:
  825. scope = ldap.SCOPE_SUBTREE
  826. group_info = None
  827. try:
  828. group_info = connection.find_groups(groupname_pattern, find_by_dn=import_by_dn, scope=scope)
  829. except LdapSearchException, e:
  830. LOG.warn("Failed to find LDAP group: %s" % e)
  831. if not group_info:
  832. LOG.warn("Could not get LDAP details for group pattern %s" % groupname_pattern)
  833. return None
  834. groups = []
  835. for ldap_info in group_info:
  836. group, created = Group.objects.get_or_create(name=ldap_info['name'])
  837. if not created and not LdapGroup.objects.filter(group=group).exists():
  838. # This is a Hue group, and shouldn't be overwritten
  839. LOG.warn(_('There was a naming conflict while importing group %(groupname)s in pattern %(groupname_pattern)s') % {
  840. 'groupname': ldap_info['name'],
  841. 'groupname_pattern': groupname_pattern
  842. })
  843. return None
  844. LdapGroup.objects.get_or_create(group=group)
  845. group.user_set.clear()
  846. # Find members and posix members for group and subgoups
  847. # Import users and groups
  848. max_count = recursive_import_members and desktop.conf.LDAP.NESTED_MEMBERS_SEARCH_DEPTH.get() or 1
  849. if import_members:
  850. _import_ldap_members(connection, group, ldap_info, max_count=max_count, failed_users=failed_users)
  851. # Sync users
  852. if sync_users:
  853. _sync_ldap_members(connection, group, ldap_info, max_count=max_count, failed_users=failed_users)
  854. group.save()
  855. groups.append(group)
  856. return groups
  857. def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_members=False, recursive_import_members=False,
  858. sync_users=True, import_by_dn=False, failed_users=None):
  859. """
  860. Import a group from LDAP. If import_members is true, this will also import any
  861. LDAP users that exist within the group. This will use suboordinate group logic.
  862. A suboordinate group is a group that is a subentry of another group.
  863. e.g. CN=subtest,CN=test,OU=groups,DC=exampe,DC=COM is a suboordinate group of
  864. CN=test,OU=groups,DC=exampe,DC=COM
  865. """
  866. if import_by_dn:
  867. scope = ldap.SCOPE_BASE
  868. else:
  869. scope = ldap.SCOPE_SUBTREE
  870. group_info = None
  871. try:
  872. group_info = connection.find_groups(groupname_pattern, find_by_dn=import_by_dn, scope=scope)
  873. except LdapSearchException, e:
  874. LOG.warn("Could not find LDAP group: %s" % e)
  875. if not group_info:
  876. LOG.warn("Could not get LDAP details for group pattern %s" % groupname_pattern)
  877. return None
  878. groups = []
  879. for ldap_info in group_info:
  880. group, created = Group.objects.get_or_create(name=ldap_info['name'])
  881. if not created and not LdapGroup.objects.filter(group=group).exists():
  882. # This is a Hue group, and shouldn't be overwritten
  883. LOG.warn(_('There was a naming conflict while importing group %(groupname)s in pattern %(groupname_pattern)s') % {
  884. 'groupname': ldap_info['name'],
  885. 'groupname_pattern': groupname_pattern
  886. })
  887. return None
  888. LdapGroup.objects.get_or_create(group=group)
  889. group.user_set.clear()
  890. # Find members and posix members for group and subgoups
  891. members = ldap_info['members']
  892. posix_members = ldap_info['posix_members']
  893. # @TODO: Deprecate recursive_import_members as it may not be useful.
  894. if import_members:
  895. if recursive_import_members:
  896. group_info = []
  897. try:
  898. group_info = connection.find_groups(ldap_info['dn'], find_by_dn=True)
  899. except LdapSearchException, e:
  900. LOG.warn("Failed to find LDAP group: %s" % e)
  901. for sub_ldap_info in group_info:
  902. members += sub_ldap_info['members']
  903. posix_members += sub_ldap_info['posix_members']
  904. for member in members:
  905. LOG.debug("Importing user %s" % smart_str(member))
  906. group.user_set.add( *( _import_ldap_users(connection, member, import_by_dn=True, failed_users=failed_users) or [] ) )
  907. # Sync users
  908. if sync_users:
  909. for member in members:
  910. user_info = []
  911. try:
  912. user_info = connection.find_users(member, find_by_dn=True)
  913. except LdapSearchException, e:
  914. LOG.warn("Failed to find LDAP user: %s" % e)
  915. if len(user_info) > 1:
  916. LOG.warn('Found multiple users for member %s.' % member)
  917. else:
  918. for ldap_info in user_info:
  919. try:
  920. validate_username(ldap_info['username'])
  921. user = ldap_access.get_ldap_user(username=ldap_info['username'])
  922. group.user_set.add(user)
  923. except ValidationError, e:
  924. if failed_users is None:
  925. failed_users = []
  926. failed_users.append(ldap_info['username'])
  927. LOG.warn('Could not sync %s: %s' % (ldap_info['username'], e.message))
  928. except User.DoesNotExist:
  929. pass
  930. # Import/fetch posix users and groups
  931. # Posix members
  932. if posix_members:
  933. if import_members:
  934. for posix_member in posix_members:
  935. LOG.debug("Importing user %s" % str(posix_member))
  936. # posixGroup class defines 'memberUid' to be login names,
  937. # which are defined by 'uid'.
  938. user_info = None
  939. try:
  940. user_info = connection.find_users(posix_member, search_attr='uid', user_name_attr=desktop.conf.LDAP.USERS.USER_NAME_ATTR.get(), find_by_dn=False)
  941. except LdapSearchException, e:
  942. LOG.warn("Failed to find LDAP user: %s" % e)
  943. if user_info:
  944. users = _import_ldap_users_info(connection, user_info, import_by_dn=False, failed_users=failed_users)
  945. if users:
  946. LOG.debug("Adding member %s represented as users (should be a single user) %s to group %s" % (str(posix_member), str(users), str(group.name)))
  947. group.user_set.add(*users)
  948. if sync_users:
  949. for posix_member in posix_members:
  950. user_info = []
  951. try:
  952. user_info = connection.find_users(posix_member, search_attr='uid', user_name_attr=desktop.conf.LDAP.USERS.USER_NAME_ATTR.get(), find_by_dn=False)
  953. except LdapSearchException, e:
  954. LOG.warn("Failed to find LDAP user: %s" % e)
  955. if len(user_info) > 1:
  956. LOG.warn('Found multiple users for member %s.' % posix_member)
  957. else:
  958. for ldap_info in user_info:
  959. try:
  960. validate_username(ldap_info['username'])
  961. user = ldap_access.get_ldap_user(username=ldap_info['username'])
  962. group.user_set.add(user)
  963. except ValidationError, e:
  964. if failed_users is None:
  965. failed_users = []
  966. failed_users.append(ldap_info['username'])
  967. LOG.warn('Could not sync %s: %s' % (ldap_info['username'], e.message))
  968. except User.DoesNotExist:
  969. pass
  970. group.save()
  971. groups.append(group)
  972. return groups
  973. def _import_ldap_groups(connection, groupname_pattern, import_members=False, recursive_import_members=False,
  974. sync_users=True, import_by_dn=False, failed_users=None):
  975. """
  976. Import a group from LDAP. If import_members is true, this will also import any
  977. LDAP users that exist within the group.
  978. """
  979. if desktop.conf.LDAP.SUBGROUPS.get().lower() == 'suboordinate':
  980. return _import_ldap_suboordinate_groups(connection=connection,
  981. groupname_pattern=groupname_pattern,
  982. import_members=import_members,
  983. recursive_import_members=recursive_import_members,
  984. sync_users=sync_users,
  985. import_by_dn=import_by_dn,
  986. failed_users=failed_users)
  987. else:
  988. return _import_ldap_nested_groups(connection=connection,
  989. groupname_pattern=groupname_pattern,
  990. import_members=import_members,
  991. recursive_import_members=recursive_import_members,
  992. sync_users=sync_users,
  993. import_by_dn=import_by_dn,
  994. failed_users=failed_users)
  995. def _get_failed_operation_text(username, operation):
  996. return '%(username)s is not allowed to perform %(operation)s' % {'username': username, 'operation': operation}