forms.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  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 logging
  18. import re
  19. import django.contrib.auth.forms
  20. from django import forms
  21. from django.contrib.auth.models import User, Group
  22. from django.forms import ValidationError
  23. from django.forms.util import ErrorList
  24. from django.utils.translation import get_language, ugettext as _, ugettext_lazy as _t
  25. from desktop import conf as desktop_conf
  26. from desktop.lib.django_util import get_username_re_rule, get_groupname_re_rule
  27. from desktop.settings import LANGUAGES
  28. from useradmin.models import GroupPermission, HuePermission
  29. from useradmin.models import get_default_user_group
  30. from useradmin.password_policy import get_password_validators
  31. LOG = logging.getLogger(__name__)
  32. def get_server_choices():
  33. if desktop_conf.LDAP.LDAP_SERVERS.get():
  34. return [(ldap_server_record_key, ldap_server_record_key) for ldap_server_record_key in desktop_conf.LDAP.LDAP_SERVERS.get()]
  35. else:
  36. return []
  37. def validate_dn(dn):
  38. if not dn:
  39. raise ValidationError(_('Full Distinguished Name required.'))
  40. def validate_username(username_pattern):
  41. validator = re.compile(r"^%s$" % get_username_re_rule())
  42. if not username_pattern:
  43. raise ValidationError(_('Username is required.'))
  44. if len(username_pattern) > 30:
  45. raise ValidationError(_('Username must be fewer than 30 characters.'))
  46. if not validator.match(username_pattern):
  47. raise ValidationError(_("Username must not contain whitespaces and ':'"))
  48. def validate_groupname(groupname_pattern):
  49. validator = re.compile(r"^%s$" % get_groupname_re_rule())
  50. if not groupname_pattern:
  51. raise ValidationError(_('Group name required.'))
  52. if len(groupname_pattern) > 80:
  53. raise ValidationError(_('Group name must be 80 characters or fewer.'))
  54. if not validator.match(groupname_pattern):
  55. raise ValidationError(_("Group name can be any character as long as it's 80 characters or fewer."))
  56. def validate_first_name(first_name):
  57. if first_name and len(first_name) > 30:
  58. raise ValidationError(_('first_name must be fewer than 30 characters.'))
  59. def validate_last_name(last_name):
  60. if last_name and len(last_name) > 30:
  61. raise ValidationError(_('last_name must be fewer than 30 characters.'))
  62. class UserChangeForm(django.contrib.auth.forms.UserChangeForm):
  63. """
  64. This is similar, but not quite the same as djagno.contrib.auth.forms.UserChangeForm
  65. and UserCreationForm.
  66. """
  67. GENERIC_VALIDATION_ERROR = _("Username or password is invalid.")
  68. username = forms.RegexField(
  69. label=_t("Username"),
  70. max_length=30,
  71. regex='^%s$' % (get_username_re_rule(),),
  72. help_text = _t("Required. 30 characters or fewer. No whitespaces or colons."),
  73. error_messages = {'invalid': _t("Whitespaces and ':' not allowed") })
  74. password1 = forms.CharField(label=_t("New Password"),
  75. widget=forms.
  76. PasswordInput,
  77. required=False,
  78. validators=get_password_validators())
  79. password2 = forms.CharField(label=_t("Password confirmation"),
  80. widget=forms.PasswordInput,
  81. required=False,
  82. validators=get_password_validators())
  83. password_old = forms.CharField(label=_t("Current password"), widget=forms.PasswordInput, required=False)
  84. ensure_home_directory = forms.BooleanField(label=_t("Create home directory"),
  85. help_text=_t("Create home directory if one doesn't already exist."),
  86. initial=True,
  87. required=False)
  88. language = forms.ChoiceField(label=_t("Language Preference"),
  89. choices=LANGUAGES,
  90. required=False)
  91. unlock_account = forms.BooleanField(label=_t("Unlock Account"),
  92. help_text=_t("Unlock user's account for login."),
  93. initial=False,
  94. required=False)
  95. class Meta(django.contrib.auth.forms.UserChangeForm.Meta):
  96. fields = ["username", "first_name", "last_name", "email", "ensure_home_directory"]
  97. def __init__(self, *args, **kwargs):
  98. super(UserChangeForm, self).__init__(*args, **kwargs)
  99. if self.instance.id:
  100. self.fields['username'].widget.attrs['readonly'] = True
  101. if 'desktop.auth.backend.LdapBackend' in desktop_conf.AUTH.BACKEND.get():
  102. self.fields['password1'].widget.attrs['readonly'] = True
  103. self.fields['password2'].widget.attrs['readonly'] = True
  104. self.fields['password_old'].widget.attrs['readonly'] = True
  105. self.fields['first_name'].widget.attrs['readonly'] = True
  106. self.fields['last_name'].widget.attrs['readonly'] = True
  107. self.fields['email'].widget.attrs['readonly'] = True
  108. if 'is_active' in self.fields:
  109. self.fields['is_active'].widget.attrs['readonly'] = True
  110. if 'is_superuser' in self.fields:
  111. self.fields['is_superuser'].widget.attrs['readonly'] = True
  112. if 'unlock_account' in self.fields:
  113. self.fields['unlock_account'].widget.attrs['readonly'] = True
  114. if 'groups' in self.fields:
  115. self.fields['groups'].widget.attrs['readonly'] = True
  116. def clean_username(self):
  117. username = self.cleaned_data["username"]
  118. if self.instance.username == username:
  119. return username
  120. try:
  121. User._default_manager.get(username=username)
  122. except User.DoesNotExist:
  123. return username
  124. raise forms.ValidationError(self.GENERIC_VALIDATION_ERROR, code='duplicate_username')
  125. def clean_password(self):
  126. return self.cleaned_data["password"]
  127. def clean_password2(self):
  128. password1 = self.cleaned_data.get("password1", "")
  129. password2 = self.cleaned_data["password2"]
  130. if password1 != password2:
  131. raise forms.ValidationError(_t("Passwords do not match."))
  132. return password2
  133. def clean_password1(self):
  134. password = self.cleaned_data.get("password1", "")
  135. if self.instance.id is None and password == "":
  136. raise forms.ValidationError(_("You must specify a password when creating a new user."))
  137. return self.cleaned_data.get("password1", "")
  138. def clean_password_old(self):
  139. if self.instance.id is not None:
  140. password1 = self.cleaned_data.get("password1", "")
  141. password_old = self.cleaned_data.get("password_old", "")
  142. if password1 != '' and not self.instance.check_password(password_old):
  143. raise forms.ValidationError(self.GENERIC_VALIDATION_ERROR)
  144. return self.cleaned_data.get("password_old", "")
  145. def save(self, commit=True):
  146. """
  147. Update password if it's set.
  148. """
  149. user = super(UserChangeForm, self).save(commit=False)
  150. if self.cleaned_data["password1"]:
  151. user.set_password(self.cleaned_data["password1"])
  152. if commit:
  153. user.save()
  154. # groups must be saved after the user
  155. self.save_m2m()
  156. return user
  157. class PasswordChangeForm(UserChangeForm):
  158. """
  159. This inherits from UserChangeForm to allow for forced password change on first login
  160. """
  161. class Meta(UserChangeForm.Meta):
  162. exclude = ('first_name', 'last_name', 'email')
  163. def __init__(self, *args, **kwargs):
  164. super(PasswordChangeForm, self).__init__(*args, **kwargs)
  165. self.fields.pop('ensure_home_directory')
  166. class SuperUserChangeForm(UserChangeForm):
  167. class Meta(UserChangeForm.Meta):
  168. fields = ["username", "is_active"] + UserChangeForm.Meta.fields + ["is_superuser", "unlock_account", "groups"]
  169. def __init__(self, *args, **kwargs):
  170. super(SuperUserChangeForm, self).__init__(*args, **kwargs)
  171. if self.instance.id:
  172. # If the user exists already, we'll use its current group memberships
  173. self.initial['groups'] = set(self.instance.groups.all())
  174. else:
  175. # If his is a new user, suggest the default group
  176. default_group = get_default_user_group()
  177. if default_group is not None:
  178. self.initial['groups'] = set([default_group])
  179. else:
  180. self.initial['groups'] = []
  181. class AddLdapUsersForm(forms.Form):
  182. username_pattern = forms.CharField(
  183. label=_t("Username"),
  184. help_text=_t("Required. 30 characters or fewer with username. 64 characters or fewer with DN. No whitespaces or colons."),
  185. error_messages={'invalid': _t("Whitespaces and ':' not allowed")})
  186. dn = forms.BooleanField(label=_t("Distinguished name"),
  187. help_text=_t("Whether or not the user should be imported by "
  188. "distinguished name."),
  189. initial=False,
  190. required=False)
  191. ensure_home_directory = forms.BooleanField(label=_t("Create home directory"),
  192. help_text=_t("Create home directory for user if one doesn't already exist."),
  193. initial=True,
  194. required=False)
  195. def __init__(self, *args, **kwargs):
  196. super(AddLdapUsersForm, self).__init__(*args, **kwargs)
  197. if get_server_choices():
  198. self.fields['server'] = forms.ChoiceField(choices=get_server_choices(), required=True)
  199. def clean(self):
  200. cleaned_data = super(AddLdapUsersForm, self).clean()
  201. username_pattern = cleaned_data.get("username_pattern")
  202. dn = cleaned_data.get("dn")
  203. try:
  204. if dn:
  205. validate_dn(username_pattern)
  206. else:
  207. validate_username(username_pattern)
  208. except ValidationError, e:
  209. errors = self._errors.setdefault('username_pattern', ErrorList())
  210. errors.append(e.message)
  211. raise forms.ValidationError(e.message)
  212. return cleaned_data
  213. class AddLdapGroupsForm(forms.Form):
  214. groupname_pattern = forms.CharField(
  215. label=_t("Name"),
  216. max_length=256,
  217. help_text=_t("Required. 256 characters or fewer."),
  218. error_messages={'invalid': _t("256 characters or fewer.") })
  219. dn = forms.BooleanField(label=_t("Distinguished name"),
  220. help_text=_t("Whether or not the group should be imported by "
  221. "distinguished name."),
  222. initial=False,
  223. required=False)
  224. import_members = forms.BooleanField(label=_t('Import new members'),
  225. help_text=_t('Import unimported or new users from the group.'),
  226. initial=False,
  227. required=False)
  228. ensure_home_directories = forms.BooleanField(label=_t('Create home directories'),
  229. help_text=_t('Create home directories for every member imported, if members are being imported.'),
  230. initial=True,
  231. required=False)
  232. import_members_recursive = forms.BooleanField(label=_t('Import new members from all subgroups'),
  233. help_text=_t('Import unimported or new users from the all subgroups.'),
  234. initial=False,
  235. required=False)
  236. def __init__(self, *args, **kwargs):
  237. super(AddLdapGroupsForm, self).__init__(*args, **kwargs)
  238. if get_server_choices():
  239. self.fields['server'] = forms.ChoiceField(choices=get_server_choices(), required=True)
  240. def clean(self):
  241. cleaned_data = super(AddLdapGroupsForm, self).clean()
  242. groupname_pattern = cleaned_data.get("groupname_pattern")
  243. dn = cleaned_data.get("dn")
  244. try:
  245. if dn:
  246. validate_dn(groupname_pattern)
  247. else:
  248. validate_groupname(groupname_pattern)
  249. except ValidationError, e:
  250. errors = self._errors.setdefault('groupname_pattern', ErrorList())
  251. errors.append(e.message)
  252. raise forms.ValidationError(e.message)
  253. return cleaned_data
  254. class GroupEditForm(forms.ModelForm):
  255. """
  256. Form to manipulate a group. This manages the group name and its membership.
  257. """
  258. GROUPNAME = re.compile('^%s$' % get_groupname_re_rule())
  259. class Meta:
  260. model = Group
  261. fields = ("name",)
  262. def clean_name(self):
  263. # Note that the superclass doesn't have a clean_name method.
  264. data = self.cleaned_data["name"]
  265. if not self.GROUPNAME.match(data):
  266. raise forms.ValidationError(_("Group name may only contain letters, " +
  267. "numbers, hyphens or underscores."))
  268. return data
  269. def __init__(self, *args, **kwargs):
  270. super(GroupEditForm, self).__init__(*args, **kwargs)
  271. if self.instance.id:
  272. self.fields['name'].widget.attrs['readonly'] = True
  273. initial_members = User.objects.filter(groups=self.instance).order_by('username')
  274. initial_perms = HuePermission.objects.filter(grouppermission__group=self.instance).order_by('app','description')
  275. else:
  276. initial_members = []
  277. initial_perms = []
  278. self.fields["members"] = _make_model_field(_("members"), initial_members, User.objects.order_by('username'))
  279. self.fields["permissions"] = _make_model_field(_("permissions"), initial_perms, HuePermission.objects.order_by('app','description'))
  280. def _compute_diff(self, field_name):
  281. current = set(self.fields[field_name].initial_objs)
  282. updated = set(self.cleaned_data[field_name])
  283. delete = current.difference(updated)
  284. add = updated.difference(current)
  285. return delete, add
  286. def save(self):
  287. super(GroupEditForm, self).save()
  288. self._save_members()
  289. self._save_permissions()
  290. def _save_members(self):
  291. delete_membership, add_membership = self._compute_diff("members")
  292. for user in delete_membership:
  293. user.groups.remove(self.instance)
  294. user.save()
  295. for user in add_membership:
  296. user.groups.add(self.instance)
  297. user.save()
  298. def _save_permissions(self):
  299. delete_permission, add_permission = self._compute_diff("permissions")
  300. for perm in delete_permission:
  301. GroupPermission.objects.get(group=self.instance, hue_permission=perm).delete()
  302. for perm in add_permission:
  303. GroupPermission.objects.create(group=self.instance, hue_permission=perm)
  304. class PermissionsEditForm(forms.ModelForm):
  305. """
  306. Form to manage the set of groups that have a particular permission.
  307. """
  308. class Meta:
  309. model = Group
  310. fields = ()
  311. def __init__(self, *args, **kwargs):
  312. super(PermissionsEditForm, self).__init__(*args, **kwargs)
  313. if self.instance.id:
  314. initial_groups = Group.objects.filter(grouppermission__hue_permission=self.instance).order_by('name')
  315. else:
  316. initial_groups = []
  317. self.fields["groups"] = _make_model_field(_("groups"), initial_groups, Group.objects.order_by('name'))
  318. def _compute_diff(self, field_name):
  319. current = set(self.fields[field_name].initial_objs)
  320. updated = set(self.cleaned_data[field_name])
  321. delete = current.difference(updated)
  322. add = updated.difference(current)
  323. return delete, add
  324. def save(self):
  325. self._save_permissions()
  326. def _save_permissions(self):
  327. delete_group, add_group = self._compute_diff("groups")
  328. for group in delete_group:
  329. GroupPermission.objects.get(group=group, hue_permission=self.instance).delete()
  330. for group in add_group:
  331. GroupPermission.objects.create(group=group, hue_permission=self.instance)
  332. def _make_model_field(label, initial, choices, multi=True):
  333. """ Creates multiple choice field with given query object as choices. """
  334. if multi:
  335. field = forms.models.ModelMultipleChoiceField(choices, required=False)
  336. field.initial_objs = initial
  337. field.initial = [ obj.pk for obj in initial ]
  338. field.label = label
  339. else:
  340. field = forms.models.ModelChoiceField(choices, required=False)
  341. field.initial_obj = initial
  342. if initial:
  343. field.initial = initial.pk
  344. return field
  345. class SyncLdapUsersGroupsForm(forms.Form):
  346. ensure_home_directory = forms.BooleanField(label=_t("Create Home Directories"),
  347. help_text=_t("Create home directory for every user, if one doesn't already exist."),
  348. initial=True,
  349. required=False)
  350. def __init__(self, *args, **kwargs):
  351. super(SyncLdapUsersGroupsForm, self).__init__(*args, **kwargs)
  352. if get_server_choices():
  353. self.fields['server'] = forms.ChoiceField(choices=get_server_choices(), required=True)