fcce68a914
Closes-Bug: #2066115 Change-Id: Ica10eb749b48410583cb34bfa2fda0433a26c664 Signed-off-by: MinhNLH2 <minh.nlh.work@gmail.com>
344 lines
14 KiB
Python
344 lines
14 KiB
Python
# Copyright 2012 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# All Rights Reserved.
|
|
#
|
|
# Copyright 2012 Nebula, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import collections
|
|
import logging
|
|
import re
|
|
|
|
from django.conf import settings
|
|
from django.forms import ValidationError
|
|
from django import http
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.views.decorators.debug import sensitive_variables
|
|
|
|
from horizon import exceptions
|
|
from horizon import forms
|
|
from horizon import messages
|
|
from horizon.utils import functions as utils
|
|
from horizon.utils import validators
|
|
|
|
from openstack_dashboard import api
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class PasswordMixin(forms.SelfHandlingForm):
|
|
password = forms.RegexField(
|
|
label=_("Password"),
|
|
widget=forms.PasswordInput(render_value=False),
|
|
regex=validators.password_validator(),
|
|
error_messages={'invalid': validators.password_validator_msg()})
|
|
confirm_password = forms.CharField(
|
|
label=_("Confirm Password"),
|
|
strip=False,
|
|
widget=forms.PasswordInput(render_value=False))
|
|
no_autocomplete = True
|
|
|
|
def clean(self):
|
|
'''Check to make sure password fields match.'''
|
|
data = super().clean()
|
|
if 'password' in data and 'confirm_password' in data:
|
|
if data['password'] != data['confirm_password']:
|
|
raise ValidationError(_('Passwords do not match.'))
|
|
return data
|
|
|
|
|
|
class BaseUserForm(forms.SelfHandlingForm):
|
|
def __init__(self, request, *args, **kwargs):
|
|
super().__init__(request, *args, **kwargs)
|
|
|
|
# Populate project choices
|
|
project_choices = []
|
|
|
|
# If the user is already set (update action), list only projects which
|
|
# the user has access to.
|
|
user_id = kwargs['initial'].get('id', None)
|
|
domain_id = kwargs['initial'].get('domain_id', None)
|
|
default_project_id = kwargs['initial'].get('project', None)
|
|
|
|
try:
|
|
projects, has_more = api.keystone.tenant_list(
|
|
request, domain=domain_id)
|
|
|
|
for project in sorted(projects, key=lambda p: p.name.lower()):
|
|
if project.enabled:
|
|
project_choices.append((project.id, project.name))
|
|
if not project_choices:
|
|
project_choices.insert(0, ('', _("No available projects")))
|
|
# TODO(david-lyle): if keystoneclient is fixed to allow unsetting
|
|
# the default project, then this condition should be removed.
|
|
elif default_project_id is None:
|
|
project_choices.insert(0, ('', _("Select a project")))
|
|
self.fields['project'].choices = project_choices
|
|
|
|
except Exception:
|
|
LOG.debug("User: %s has no projects", user_id)
|
|
|
|
|
|
class AddExtraColumnMixIn(object):
|
|
def add_extra_fields(self, ordering=None):
|
|
# add extra column defined by setting
|
|
EXTRA_INFO = settings.USER_TABLE_EXTRA_INFO
|
|
for key, value in EXTRA_INFO.items():
|
|
self.fields[key] = forms.CharField(label=value,
|
|
required=False)
|
|
if ordering:
|
|
ordering.append(key)
|
|
|
|
|
|
ADD_PROJECT_URL = "horizon:identity:projects:create"
|
|
|
|
|
|
class CreateUserForm(PasswordMixin, BaseUserForm, AddExtraColumnMixIn):
|
|
# Hide the domain_id and domain_name by default
|
|
domain_id = forms.CharField(label=_("Domain ID"),
|
|
required=False,
|
|
widget=forms.HiddenInput())
|
|
domain_name = forms.CharField(label=_("Domain Name"),
|
|
required=False,
|
|
widget=forms.HiddenInput())
|
|
name = forms.CharField(max_length=255, label=_("User Name"))
|
|
description = forms.CharField(widget=forms.widgets.Textarea(
|
|
attrs={'rows': 4}),
|
|
label=_("Description"),
|
|
required=False)
|
|
email = forms.EmailField(
|
|
label=_("Email"),
|
|
required=False)
|
|
project = forms.ThemableDynamicChoiceField(label=_("Primary Project"),
|
|
required=False,
|
|
add_item_link=ADD_PROJECT_URL)
|
|
role_id = forms.ThemableChoiceField(label=_("Role"),
|
|
required=False)
|
|
enabled = forms.BooleanField(label=_("Enabled"),
|
|
required=False,
|
|
initial=True)
|
|
lock_password = forms.BooleanField(label=_("Lock password"),
|
|
required=False,
|
|
initial=False)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
roles = kwargs.pop('roles')
|
|
super().__init__(*args, **kwargs)
|
|
# Reorder form fields from multiple inheritance
|
|
ordering = ["domain_id", "domain_name", "name",
|
|
"description", "email", "password",
|
|
"confirm_password", "project", "role_id",
|
|
"enabled", "lock_password"]
|
|
self.add_extra_fields(ordering)
|
|
self.fields = collections.OrderedDict(
|
|
(key, self.fields[key]) for key in ordering)
|
|
role_choices = [
|
|
(role.id, role.name) for role in
|
|
sorted(roles, key=lambda r: r.name.lower())
|
|
]
|
|
self.fields['role_id'].choices = role_choices
|
|
|
|
# For keystone V3, display the two fields in read-only
|
|
readonlyInput = forms.TextInput(attrs={'readonly': 'readonly'})
|
|
self.fields["domain_id"].widget = readonlyInput
|
|
self.fields["domain_name"].widget = readonlyInput
|
|
|
|
# We have to protect the entire "data" dict because it contains the
|
|
# password and confirm_password strings.
|
|
@sensitive_variables('data')
|
|
def handle(self, request, data):
|
|
domain = api.keystone.get_default_domain(self.request, False)
|
|
try:
|
|
LOG.info('Creating user with name "%s"', data['name'])
|
|
desc = data["description"]
|
|
|
|
# add extra information
|
|
EXTRA_INFO = settings.USER_TABLE_EXTRA_INFO
|
|
kwargs = dict((key, data.get(key)) for key in EXTRA_INFO)
|
|
|
|
if "lock_password" in data:
|
|
kwargs.update({'options':
|
|
{'lock_password': data['lock_password']}})
|
|
|
|
new_user = \
|
|
api.keystone.user_create(request,
|
|
name=data['name'],
|
|
email=data.get('email') or None,
|
|
description=desc or None,
|
|
password=data['password'],
|
|
project=data.get('project') or None,
|
|
enabled=data['enabled'],
|
|
domain=domain.id,
|
|
**kwargs)
|
|
messages.success(request,
|
|
_('User "%s" was successfully created.')
|
|
% data['name'])
|
|
if data['project'] and data['role_id']:
|
|
roles = api.keystone.roles_for_user(request,
|
|
new_user.id,
|
|
data['project']) or []
|
|
assigned = [role for role in roles if role.id == str(
|
|
data['role_id'])]
|
|
if not assigned:
|
|
try:
|
|
api.keystone.add_tenant_user_role(request,
|
|
data['project'],
|
|
new_user.id,
|
|
data['role_id'])
|
|
except Exception:
|
|
exceptions.handle(request,
|
|
_('Unable to add user '
|
|
'to primary project.'))
|
|
return new_user
|
|
except exceptions.Conflict:
|
|
msg = _('User name "%s" is already used.') % data['name']
|
|
messages.error(request, msg)
|
|
except Exception:
|
|
exceptions.handle(request, _('Unable to create user.'))
|
|
|
|
|
|
class UpdateUserForm(BaseUserForm, AddExtraColumnMixIn):
|
|
# Hide the domain_id and domain_name by default
|
|
domain_id = forms.CharField(label=_("Domain ID"),
|
|
required=False,
|
|
widget=forms.HiddenInput())
|
|
domain_name = forms.CharField(label=_("Domain Name"),
|
|
required=False,
|
|
widget=forms.HiddenInput())
|
|
id = forms.CharField(label=_("ID"), widget=forms.HiddenInput)
|
|
name = forms.CharField(max_length=255, label=_("User Name"))
|
|
description = forms.CharField(widget=forms.widgets.Textarea(
|
|
attrs={'rows': 4}),
|
|
label=_("Description"),
|
|
required=False)
|
|
email = forms.EmailField(
|
|
label=_("Email"),
|
|
required=False)
|
|
project = forms.ThemableChoiceField(label=_("Primary Project"),
|
|
required=False)
|
|
|
|
lock_password = forms.BooleanField(label=_("Lock password"),
|
|
required=False,
|
|
initial=False)
|
|
|
|
def __init__(self, request, *args, **kwargs):
|
|
super().__init__(request, *args, **kwargs)
|
|
self.add_extra_fields()
|
|
if api.keystone.keystone_can_edit_user() is False:
|
|
for field in ('name', 'email'):
|
|
self.fields.pop(field)
|
|
readonlyInput = forms.TextInput(attrs={'readonly': 'readonly'})
|
|
self.fields["domain_id"].widget = readonlyInput
|
|
self.fields["domain_name"].widget = readonlyInput
|
|
|
|
def handle(self, request, data):
|
|
user = data.pop('id')
|
|
|
|
data.pop('domain_id')
|
|
data.pop('domain_name')
|
|
|
|
if 'project' not in self.changed_data:
|
|
data.pop('project')
|
|
|
|
if 'description' not in self.changed_data:
|
|
data.pop('description')
|
|
if 'lock_password' in data:
|
|
data.update({'options': {'lock_password': data['lock_password']}})
|
|
data.pop('lock_password')
|
|
|
|
try:
|
|
if "email" in data:
|
|
data['email'] = data['email']
|
|
response = api.keystone.user_update(request, user, **data)
|
|
messages.success(request,
|
|
_('User "%s" has been updated '
|
|
'successfully.') % data['name'])
|
|
except exceptions.Conflict:
|
|
msg = _('User name "%s" is already used.') % data['name']
|
|
messages.error(request, msg)
|
|
return False
|
|
except Exception:
|
|
response = exceptions.handle(request, ignore=True)
|
|
messages.error(request, _('Unable to update the user.'))
|
|
|
|
if isinstance(response, http.HttpResponse):
|
|
return response
|
|
|
|
return True
|
|
|
|
|
|
class ChangePasswordForm(PasswordMixin, forms.SelfHandlingForm):
|
|
id = forms.CharField(widget=forms.HiddenInput)
|
|
name = forms.CharField(
|
|
label=_("User Name"),
|
|
widget=forms.TextInput(attrs={'readonly': 'readonly'}),
|
|
required=False)
|
|
|
|
def __init__(self, request, *args, **kwargs):
|
|
super().__init__(request, *args, **kwargs)
|
|
|
|
if settings.ENFORCE_PASSWORD_CHECK:
|
|
self.fields["admin_password"] = forms.CharField(
|
|
label=_("Admin Password"),
|
|
strip=False,
|
|
widget=forms.PasswordInput(render_value=False))
|
|
# Reorder form fields from multiple inheritance
|
|
self.field_order = ["id", "name", "admin_password",
|
|
"password", "confirm_password"]
|
|
|
|
@sensitive_variables('data', 'password', 'admin_password')
|
|
def handle(self, request, data):
|
|
user_id = data.pop('id')
|
|
password = data.pop('password')
|
|
admin_password = None
|
|
|
|
# Throw away the password confirmation, we're done with it.
|
|
data.pop('confirm_password', None)
|
|
|
|
# Verify admin password before changing user password
|
|
if settings.ENFORCE_PASSWORD_CHECK:
|
|
admin_password = data.pop('admin_password')
|
|
if not api.keystone.user_verify_admin_password(request,
|
|
admin_password):
|
|
self.api_error(_('The admin password is incorrect.'))
|
|
return False
|
|
|
|
try:
|
|
response = api.keystone.user_update_password(
|
|
request, user_id, password, admin=False)
|
|
if user_id == request.user.id:
|
|
return utils.logout_with_message(
|
|
request,
|
|
_('Password changed. Please log in to continue.'),
|
|
redirect=False)
|
|
messages.success(request,
|
|
_('User password has been updated successfully.'))
|
|
except Exception as exc:
|
|
response = exceptions.handle(request, ignore=True)
|
|
match = re.match((r'The password does not match the '
|
|
r'requirements:(.*?) [(]HTTP 400[)]'), str(exc),
|
|
re.UNICODE | re.MULTILINE)
|
|
if match:
|
|
info = match.group(1)
|
|
messages.error(request, _('The password does not match the '
|
|
'requirements: %s') % info)
|
|
else:
|
|
messages.error(request,
|
|
_('Unable to update the user password.'))
|
|
|
|
if isinstance(response, http.HttpResponse):
|
|
return response
|
|
|
|
return True
|