Skip to content

Commit

Permalink
Tenant user administration upgrades.
Browse files Browse the repository at this point in the history
  * Converts tenant users to datatables. Fixes bug 922351.
  * Updates role-based adding/removing of users for Keystone master. Fixes bug 922391.
  * Corrects role addition during user creation. Fixes bug 922393.
  * Adds the ability to choose the role with which the user should be added to the tenant.

Depends on client updates in https://review.openstack.org/#change,3527

Change-Id: I4d85e41a278534d7266c6fc542c0f289a4bca0e3
  • Loading branch information
gabrielhurley committed Jan 29, 2012
1 parent f4c7374 commit ec65aed
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 212 deletions.
66 changes: 35 additions & 31 deletions horizon/horizon/api/keystone.py
Expand Up @@ -26,10 +26,12 @@
from keystoneclient.v2_0 import client as keystone_client
from keystoneclient.v2_0 import tokens

from horizon.api.base import *
from horizon import exceptions
from horizon.api import APIResourceWrapper


LOG = logging.getLogger(__name__)
DEFAULT_ROLE = None


def _get_endpoint_url(request):
Expand All @@ -47,11 +49,6 @@ class User(APIResourceWrapper):
_attrs = ['email', 'enabled', 'id', 'tenantId', 'name']


class Role(APIResourceWrapper):
"""Wrapper around keystoneclient.roles.role"""
_attrs = ['id', 'name', 'description', 'service_id']


class Services(APIResourceWrapper):
_attrs = ['disabled', 'host', 'id', 'last_update', 'stats', 'type', 'up',
'zone']
Expand Down Expand Up @@ -226,34 +223,41 @@ def user_update_tenant(request, user_id, tenant_id):
.update_tenant(user_id, tenant_id))


def _get_role(request, name):
roles = keystoneclient(request).roles.list()
for role in roles:
if role.name.lower() == name.lower():
return role

raise Exception(_('Role does not exist: %s') % name)
def role_list(request):
""" Returns a global list of available roles. """
return keystoneclient(request).roles.list()


def _get_roleref(request, user_id, tenant_id, role):
rolerefs = keystoneclient(request).roles.get_user_role_refs(user_id)
for roleref in rolerefs:
if roleref.roleId == role.id and roleref.tenantId == tenant_id:
return roleref
raise Exception(_('Role "%s" does not exist for that user on this tenant.')
% role.name)
def add_tenant_user_role(request, tenant_id, user_id, role_id):
""" Adds a role for a user on a tenant. """
return keystoneclient(request).roles.add_user_role(user_id,
role_id,
tenant_id)


def role_add_for_tenant_user(request, tenant_id, user_id, role):
role = _get_role(request, role)
return keystoneclient(request).roles.add_user_to_tenant(tenant_id,
user_id,
role.id)
def remove_tenant_user(request, tenant_id, user_id):
""" Removes all roles from a user on a tenant, removing them from it. """
client = keystoneclient(request)
roles = client.roles.roles_for_user(user_id, tenant_id)
for role in roles:
client.roles.remove_user_role(user_id, role.id, tenant_id)


def role_delete_for_tenant_user(request, tenant_id, user_id, role):
role = _get_role(request, role)
roleref = _get_roleref(request, user_id, tenant_id, role)
return keystoneclient(request).roles.remove_user_from_tenant(tenant_id,
user_id,
roleref.id)
def get_default_role(request):
"""
Gets the default role object from Keystone and saves it as a global
since this is configured in settings and should not change from request
to request. Supports lookup by name or id.
"""
global DEFAULT_ROLE
default = getattr(settings, "OPENSTACK_KEYSTONE_DEFAULT_ROLE", None)
if default and DEFAULT_ROLE is None:
try:
roles = keystoneclient(request).roles.list()
except:
exceptions.handle(request)
for role in roles:
if role.id == default or role.name == default:
DEFAULT_ROLE = role
break
return DEFAULT_ROLE
@@ -1,10 +1,25 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
<form id="form_add_tenant_user{{ user }}" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<input name="user" type="hidden" value="{{ user.id }}" />
<input name="tenant" type="hidden" value="{{ tenant_id }}" />
<input id="add_tenant_user_{{ user.id }}" class="btn small add" title="User: {{ user.id }}" type="submit" value="{% trans "Add" %}" />
</form>

{% block form_id %}add_user_form{% endblock %}
{% block form_action %}{% url horizon:syspanel:tenants:add_user tenant_id user_id %}{% endblock %}

{% block modal_id %}add_user_modal{% endblock %}
{% block modal-header %}{% trans "Add User To Tenant" %}{% endblock %}

{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description" %}:</h3>
<p>{% trans "Select the user role for the tenant." %}</p>
</div>
{% endblock %}

{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{% trans "Add" %}" />
<a href="{% url horizon:syspanel:tenants:users tenant_id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

This file was deleted.

@@ -0,0 +1,11 @@
{% extends 'syspanel/base.html' %}
{% load i18n %}
{% block title %}{% trans "Add User To Tenant" %}{% endblock %}

{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Add User To Tenant") %}
{% endblock page_header %}

{% block syspanel_main %}
{% include 'syspanel/tenants/_add_user.html' %}
{% endblock %}
Expand Up @@ -4,64 +4,15 @@

{% block page_header %}
<div class='page-header'>
<h2>{% trans "Users for Tenant" %}: <span>{{ tenant_id }}</span></h2>
<h2>{% trans "Users for Tenant" %}: <span>{{ tenant.name }}</span></h2>
</div>
{% endblock %}

{% block syspanel_main %}
<div id="usage">

{% if users %}
<table class="zebra-striped">
<tr id='headings'>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Email" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
<tbody class='main'>
{% for user in users %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td id="actions">
<ul>
<li class="form">{% include "syspanel/tenants/_remove_user.html" with form=remove_user_form %}</li>
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="message_box info">
<h2>{% trans "Info" %}</h2>
<p>T{% trans "here are currently no users for this tenant" %}</p>
</div>
{% endif %}
{% if new_users %}
<h3>{% trans "Add new users" %}</h3>
<table class="zebra-striped">
<tr id='headings'>
<th>{% trans "ID" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
<tbody class='main'>
{% for user in new_users %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td id="actions">
<ul>
<li class="form">{% include "syspanel/tenants/_add_user.html" with form=add_user_form %}</li>
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}

<div id="tenant_users_table">
{{ tenant_users_table.render }}
</div>
<div id="add_users_table">
{{ add_users_table.render }}
</div>
{% endblock %}
47 changes: 15 additions & 32 deletions horizon/horizon/dashboards/syspanel/tenants/forms.py
Expand Up @@ -21,7 +21,6 @@
import logging

from django import shortcuts
from django.conf import settings
from django.contrib import messages
from django.utils.translation import ugettext as _

Expand All @@ -34,43 +33,27 @@


class AddUser(forms.SelfHandlingForm):
user = forms.CharField()
tenant = forms.CharField()
tenant_id = forms.CharField(widget=forms.widgets.HiddenInput())
user_id = forms.CharField(widget=forms.widgets.HiddenInput())
role_id = forms.ChoiceField(label=_("Role"))

def handle(self, request, data):
try:
api.role_add_for_tenant_user(
request,
data['tenant'],
data['user'],
settings.OPENSTACK_KEYSTONE_DEFAULT_ROLE)
messages.success(request,
_('%(user)s was successfully added to %(tenant)s.')
% {"user": data['user'], "tenant": data['tenant']})
except:
exceptions.handle(request, _('Unable to add user to tenant.'))
return shortcuts.redirect('horizon:syspanel:tenants:users',
tenant_id=data['tenant'])


class RemoveUser(forms.SelfHandlingForm):
user = forms.CharField()
tenant = forms.CharField()
def __init__(self, *args, **kwargs):
roles = kwargs.pop('roles')
super(AddUser, self).__init__(*args, **kwargs)
role_choices = [(role.id, role.name) for role in roles]
self.fields['role_id'].choices = role_choices

def handle(self, request, data):
try:
api.role_delete_for_tenant_user(
request,
data['tenant'],
data['user'],
settings.OPENSTACK_KEYSTONE_DEFAULT_ROLE)
messages.success(request,
_('%(user)s was successfully removed from %(tenant)s.')
% {"user": data['user'], "tenant": data['tenant']})
api.add_tenant_user_role(request,
data['tenant_id'],
data['user_id'],
data['role_id'])
messages.success(request, _('Successfully added user to tenant.'))
except:
exceptions.handle(request, _('Unable to remove user from tenant.'))
exceptions.handle(request, _('Unable to add user to tenant.'))
return shortcuts.redirect('horizon:syspanel:tenants:users',
tenant_id=data['tenant'])
tenant_id=data['tenant_id'])


class CreateTenant(forms.SelfHandlingForm):
Expand Down
47 changes: 45 additions & 2 deletions horizon/horizon/dashboards/syspanel/tenants/tables.py
@@ -1,10 +1,13 @@
import logging

from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _

from horizon import api
from horizon import tables

from ..users.tables import UsersTable


LOG = logging.getLogger(__name__)

Expand All @@ -18,7 +21,7 @@ class ModifyQuotasLink(tables.LinkAction):

class ViewMembersLink(tables.LinkAction):
name = "users"
verbose_name = _("View Members")
verbose_name = _("Modify Users")
url = "horizon:syspanel:tenants:users"


Expand All @@ -30,7 +33,7 @@ class UsageLink(tables.LinkAction):

class EditLink(tables.LinkAction):
name = "update"
verbose_name = _("Edit")
verbose_name = _("Edit Tenant")
url = "horizon:syspanel:tenants:update"
attrs = {"class": "ajax-modal"}

Expand Down Expand Up @@ -77,3 +80,43 @@ class Meta:
row_actions = (EditLink, UsageLink, ViewMembersLink, ModifyQuotasLink,
DeleteTenantsAction)
table_actions = (TenantFilterAction, CreateLink, DeleteTenantsAction)


class RemoveUserAction(tables.BatchAction):
name = "remove_user"
action_present = _("Remove")
action_past = _("Removed")
data_type_singular = _("User")
data_type_plural = _("Users")
classes = ('danger',)

def action(self, request, user_id):
tenant_id = self.table.kwargs['tenant_id']
api.keystone.remove_tenant_user(request, tenant_id, user_id)


class TenantUsersTable(UsersTable):
class Meta:
name = "tenant_users"
verbose_name = _("Users For Tenant")
table_actions = (RemoveUserAction,)
row_actions = (RemoveUserAction,)


class AddUserAction(tables.LinkAction):
name = "add_user"
verbose_name = _("Add To Tenant")
url = "horizon:syspanel:tenants:add_user"
classes = ('ajax-modal',)

def get_link_url(self, user):
tenant_id = self.table.kwargs['tenant_id']
return reverse(self.url, args=(tenant_id, user.id))


class AddUsersTable(UsersTable):
class Meta:
name = "add_users"
verbose_name = _("Add New Users")
table_actions = ()
row_actions = (AddUserAction,)
9 changes: 6 additions & 3 deletions horizon/horizon/dashboards/syspanel/tenants/urls.py
Expand Up @@ -20,16 +20,19 @@

from django.conf.urls.defaults import patterns, url

from .views import IndexView, CreateView, UpdateView, QuotasView
from .views import (IndexView, CreateView, UpdateView, QuotasView, UsersView,
AddUserView)


urlpatterns = patterns('horizon.dashboards.syspanel.tenants.views',
url(r'^$', IndexView.as_view(), name='index'),
url(r'^create$', CreateView.as_view(), name='create'),
url(r'^(?P<tenant_id>[^/]+)/update/$',
UpdateView.as_view(), name='update'),
url(r'^(?P<tenant_id>[^/]+)/users/$', 'users', name='users'),
url(r'^(?P<tenant_id>[^/]+)/quotas/$',
QuotasView.as_view(), name='quotas'),
url(r'^(?P<tenant_id>[^/]+)/usage/$', 'usage', name='usage')
url(r'^(?P<tenant_id>[^/]+)/usage/$', 'usage', name='usage'),
url(r'^(?P<tenant_id>[^/]+)/users/$', UsersView.as_view(), name='users'),
url(r'^(?P<tenant_id>[^/]+)/users/(?P<user_id>[^/]+)/add/$',
AddUserView.as_view(), name='add_user')
)

0 comments on commit ec65aed

Please sign in to comment.