diff --git a/openstack_dashboard/api/base.py b/openstack_dashboard/api/base.py index 3dfa02ce2..b9fa31b01 100644 --- a/openstack_dashboard/api/base.py +++ b/openstack_dashboard/api/base.py @@ -33,6 +33,38 @@ __all__ = ('APIResourceWrapper', 'APIDictWrapper', LOG = logging.getLogger(__name__) +class APIVersionManager(object): + """ Object to store and manage API versioning data and utility methods. """ + + SETTINGS_KEY = "OPENSTACK_API_VERSIONS" + + def __init__(self, service_type, preferred_version=None): + self.service_type = service_type + self.preferred = preferred_version + self._active = None + self.supported = {} + + @property + def active(self): + if self._active is None: + self.get_active_version() + return self._active + + def load_supported_version(self, version, data): + self.supported[version] = data + + def get_active_version(self): + if self._active is not None: + return self.supported[self._active] + key = getattr(settings, self.SETTINGS_KEY, {}).get(self.service_type) + if key is None: + # TODO: support API version discovery here; we'll leave the setting + # in as a way of overriding the latest available version. + key = self.preferred + self._active = key + return self.supported[self._active] + + class APIResourceWrapper(object): """ Simple wrapper for api objects diff --git a/openstack_dashboard/api/keystone.py b/openstack_dashboard/api/keystone.py index 6fd6d02d4..ed22f24f0 100644 --- a/openstack_dashboard/api/keystone.py +++ b/openstack_dashboard/api/keystone.py @@ -21,18 +21,17 @@ import logging import urlparse -from pkg_resources import get_distribution from django.conf import settings +from django.contrib.auth import logout from django.utils.translation import ugettext_lazy as _ -from keystoneclient import service_catalog -from keystoneclient.v2_0 import client as keystone_client -from keystoneclient.v2_0 import tokens +from keystoneclient.exceptions import ClientException from openstack_auth.backend import KEYSTONE_CLIENT_ATTR from horizon import exceptions +from horizon import messages from openstack_dashboard.api import base @@ -41,6 +40,39 @@ LOG = logging.getLogger(__name__) DEFAULT_ROLE = None +# Set up our data structure for managing Identity API versions, and +# add a couple utility methods to it. +class IdentityAPIVersionManager(base.APIVersionManager): + def upgrade_v2_user(self, user): + if getattr(user, "project_id", None) is None: + user.project_id = getattr(user, "tenantId", None) + return user + + def get_project_manager(self, *args, **kwargs): + if VERSIONS.active < 3: + manager = keystoneclient(*args, **kwargs).tenants + else: + manager = keystoneclient(*args, **kwargs).projects + return manager + + +VERSIONS = IdentityAPIVersionManager("identity", preferred_version=3) + + +# Import from oldest to newest so that "preferred" takes correct precedence. +try: + from keystoneclient.v2_0 import client as keystone_client_v2 + VERSIONS.load_supported_version(2.0, {"client": keystone_client_v2}) +except ImportError: + pass + +try: + from keystoneclient.v3 import client as keystone_client_v3 + VERSIONS.load_supported_version(3, {"client": keystone_client_v3}) +except ImportError: + pass + + class Service(base.APIDictWrapper): """ Wrapper for a dict based on the service data from keystone. """ _attrs = ['id', 'type', 'name'] @@ -55,8 +87,7 @@ class Service(base.APIDictWrapper): def __unicode__(self): if(self.type == "identity"): return _("%(type)s (%(backend)s backend)") \ - % {"type": self.type, - "backend": keystone_backend_name()} + % {"type": self.type, "backend": keystone_backend_name()} else: return self.type @@ -66,11 +97,20 @@ class Service(base.APIDictWrapper): def _get_endpoint_url(request, endpoint_type, catalog=None): if getattr(request.user, "service_catalog", None): - return base.url_for(request, - service_type='identity', - endpoint_type=endpoint_type) - return request.session.get('region_endpoint', - getattr(settings, 'OPENSTACK_KEYSTONE_URL')) + url = base.url_for(request, + service_type='identity', + endpoint_type=endpoint_type) + else: + auth_url = getattr(settings, 'OPENSTACK_KEYSTONE_URL') + url = request.session.get('region_endpoint', auth_url) + + # TODO: When the Service Catalog no longer contains API versions + # in the endpoints this can be removed. + bits = urlparse.urlparse(url) + root = "://".join((bits.scheme, bits.netloc)) + url = "%s/v%s" % (root, VERSIONS.active) + + return url def keystoneclient(request, admin=False): @@ -105,6 +145,8 @@ def keystoneclient(request, admin=False): 'OPENSTACK_ENDPOINT_TYPE', 'internalURL') + api_version = VERSIONS.get_active_version() + # Take care of client connection caching/fetching a new client. # Admin vs. non-admin clients are cached separately for token matching. cache_attr = "_keystoneclient_admin" if admin else KEYSTONE_CLIENT_ATTR @@ -116,102 +158,168 @@ def keystoneclient(request, admin=False): endpoint = _get_endpoint_url(request, endpoint_type) insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) LOG.debug("Creating a new keystoneclient connection to %s." % endpoint) - - conn = keystone_client.Client( - token=user.token.id, endpoint=endpoint, - original_ip=request.environ.get('REMOTE_ADDR', ''), - insecure=insecure) + remote_addr = request.environ.get('REMOTE_ADDR', '') + conn = api_version['client'].Client(token=user.token.id, + endpoint=endpoint, + original_ip=remote_addr, + insecure=insecure, + debug=settings.DEBUG) setattr(request, cache_attr, conn) return conn -def tenant_create(request, tenant_name, description, enabled): - return keystoneclient(request, admin=True).tenants.create(tenant_name, - description, - enabled) - - -def tenant_get(request, tenant_id, admin=False): - return keystoneclient(request, admin=admin).tenants.get(tenant_id) - - -def tenant_delete(request, tenant_id): - keystoneclient(request, admin=True).tenants.delete(tenant_id) - - -def tenant_list(request, admin=False): - return keystoneclient(request, admin=admin).tenants.list() - - -def tenant_update(request, tenant_id, tenant_name, description, enabled): - return keystoneclient(request, admin=True).tenants.update(tenant_id, - tenant_name, - description, - enabled) - - -def token_create_scoped(request, tenant, token): - ''' - Creates a scoped token using the tenant id and unscoped token; retrieves - the service catalog for the given tenant. - ''' - if hasattr(request, '_keystone'): - del request._keystone - c = keystoneclient(request) - raw_token = c.tokens.authenticate(tenant_id=tenant, - token=token, - return_raw=True) - c.service_catalog = service_catalog.ServiceCatalog(raw_token) - if request.user.is_superuser: - c.management_url = c.service_catalog.url_for(service_type='identity', - endpoint_type='adminURL') +def tenant_create(request, name, description=None, enabled=None, domain=None): + manager = VERSIONS.get_project_manager(request, admin=True) + if VERSIONS.active < 3: + return manager.create(name, description, enabled) else: - endpoint_type = getattr(settings, - 'OPENSTACK_ENDPOINT_TYPE', - 'internalURL') - c.management_url = c.service_catalog.url_for( - service_type='identity', endpoint_type=endpoint_type) - scoped_token = tokens.Token(tokens.TokenManager, raw_token) - return scoped_token + return manager.create(name, domain, + description=description, + enabled=enabled) -def user_list(request, tenant_id=None): - return keystoneclient(request, admin=True).users.list(tenant_id=tenant_id) +# TODO(gabriel): Is there ever a valid case for admin to be false here? +# A quick search through the codebase reveals that it's always called with +# admin=true so I suspect we could eliminate it entirely as with the other +# tenant commands. +def tenant_get(request, project, admin=True): + manager = VERSIONS.get_project_manager(request, admin=admin) + return manager.get(project) -def user_create(request, user_id, email, password, tenant_id, enabled): - return keystoneclient(request, admin=True).users.create(user_id, - password, - email, - tenant_id, - enabled) +def tenant_delete(request, project): + manager = VERSIONS.get_project_manager(request, admin=True) + return manager.delete(project) + + +def tenant_list(request, domain=None, user=None): + manager = VERSIONS.get_project_manager(request, admin=True) + if VERSIONS.active < 3: + return manager.list() + else: + return manager.list(domain=domain, user=user) + + +def tenant_update(request, project, name=None, description=None, + enabled=None, domain=None): + manager = VERSIONS.get_project_manager(request, admin=True) + if VERSIONS.active < 3: + return manager.update(project, name, description, enabled) + else: + return manager.update(project, name=name, description=description, + enabled=enabled, domain=domain) + + +def user_list(request, project=None, domain=None, group=None): + if VERSIONS.active < 3: + kwargs = {"tenant_id": project} + else: + kwargs = { + "project": project, + "domain": domain, + "group": group + } + users = keystoneclient(request, admin=True).users.list(**kwargs) + return [VERSIONS.upgrade_v2_user(user) for user in users] + + +def user_create(request, name=None, email=None, password=None, project=None, + enabled=None, domain=None): + manager = keystoneclient(request, admin=True).users + if VERSIONS.active < 3: + user = manager.create(name, password, email, project, enabled) + return VERSIONS.upgrade_v2_user(user) + else: + return manager.create(name, password=password, email=email, + project=project, enabled=enabled, domain=domain) def user_delete(request, user_id): - keystoneclient(request, admin=True).users.delete(user_id) + return keystoneclient(request, admin=True).users.delete(user_id) def user_get(request, user_id, admin=True): - return keystoneclient(request, admin=admin).users.get(user_id) + user = keystoneclient(request, admin=admin).users.get(user_id) + return VERSIONS.upgrade_v2_user(user) def user_update(request, user, **data): - return keystoneclient(request, admin=True).users.update(user, **data) + manager = keystoneclient(request, admin=True).users + error = None + + if not keystone_can_edit_user(): + raise ClientException(405, _("Identity service does not allow " + "editing user data.")) + + # The v2 API updates user model, password and default project separately + if VERSIONS.active < 3: + password = data.pop('password') + project = data.pop('project') + + # Update user details + try: + user = manager.update(user, **data) + except: + error = exceptions.handle(request, ignore=True) + + # Update default tenant + try: + user_update_tenant(request, user, project) + user.tenantId = project + except: + error = exceptions.handle(request, ignore=True) + + # Check for existing roles + # Show a warning if no role exists for the project + user_roles = roles_for_user(request, user, project) + if not user_roles: + messages.warning(request, + _('User %s has no role defined for ' + 'that project.') + % data.get('name', None)) + + # If present, update password + # FIXME(gabriel): password change should be its own form + view + if password: + try: + user_update_password(request, user, password) + if user == request.user.id: + logout(request) + except: + error = exceptions.handle(request, ignore=True) + + if error is not None: + raise error + + # v3 API is so much simpler... + else: + user = manager.update(user, **data) + + return VERSIONS.upgrade_v2_user(user) -def user_update_enabled(request, user_id, enabled): - return keystoneclient(request, admin=True).users.update_enabled(user_id, - enabled) +def user_update_enabled(request, user, enabled): + manager = keystoneclient(request, admin=True).users + if VERSIONS.active < 3: + return manager.update_enabled(user, enabled) + else: + return manager.update(user, enabled=enabled) -def user_update_password(request, user_id, password, admin=True): - return keystoneclient(request, admin=admin).users.update_password(user_id, - password) +def user_update_password(request, user, password, admin=True): + manager = keystoneclient(request, admin=admin).users + if VERSIONS.active < 3: + return manager.update_password(user, password) + else: + return manager.update(user, password=password) -def user_update_tenant(request, user_id, tenant_id, admin=True): - return keystoneclient(request, admin=admin).users.update_tenant(user_id, - tenant_id) +def user_update_tenant(request, user, project, admin=True): + manager = keystoneclient(request, admin=admin).users + if VERSIONS.active < 3: + return manager.update_tenant(user, project) + else: + return manager.update(user, project=project) def role_list(request): @@ -220,29 +328,42 @@ def role_list(request): def roles_for_user(request, user, project): - return keystoneclient(request, admin=True).roles.roles_for_user(user, - project) + manager = keystoneclient(request, admin=True).roles + if VERSIONS.active < 3: + return manager.roles_for_user(user, project) + else: + return manager.list(user=user, project=project) -def add_tenant_user_role(request, tenant_id, user_id, role_id): +def add_tenant_user_role(request, project=None, user=None, role=None, + group=None, domain=None): """ Adds a role for a user on a tenant. """ - return keystoneclient(request, admin=True).roles.add_user_role(user_id, - role_id, - tenant_id) + manager = keystoneclient(request, admin=True).roles + if VERSIONS.active < 3: + return manager.add_user_role(user, role, project) + else: + return manager.grant(role, user=user, project=project, + group=group, domain=domain) -def remove_tenant_user_role(request, tenant_id, user_id, role_id): +def remove_tenant_user_role(request, project=None, user=None, role=None, + group=None, domain=None): """ Removes a given single role for a user from a tenant. """ - client = keystoneclient(request, admin=True) - client.roles.remove_user_role(user_id, role_id, tenant_id) + manager = keystoneclient(request, admin=True).roles + if VERSIONS.active < 3: + return manager.remove_user_role(user, role, project) + else: + return manager.revoke(role, user=user, project=project, + group=group, domain=domain) -def remove_tenant_user(request, tenant_id, user_id): +def remove_tenant_user(request, project=None, user=None, domain=None): """ Removes all roles from a user on a tenant, removing them from it. """ client = keystoneclient(request, admin=True) - roles = client.roles.roles_for_user(user_id, tenant_id) + roles = client.roles.roles_for_user(user, project) for role in roles: - client.roles.remove_user_role(user_id, role.id, tenant_id) + remove_tenant_user_role(request, user=user, role=role.id, + project=project, domain=domain) def get_default_role(request): diff --git a/openstack_dashboard/dashboards/admin/instances/tests.py b/openstack_dashboard/dashboards/admin/instances/tests.py index eaf046933..5b39f4a03 100644 --- a/openstack_dashboard/dashboards/admin/instances/tests.py +++ b/openstack_dashboard/dashboards/admin/instances/tests.py @@ -33,7 +33,7 @@ class InstanceViewTest(test.BaseAdminViewTests): servers = self.servers.list() flavors = self.flavors.list() tenants = self.tenants.list() - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True).\ + api.keystone.tenant_list(IsA(http.HttpRequest)).\ AndReturn(tenants) search_opts = {'marker': None, 'paginate': True} api.nova.server_list(IsA(http.HttpRequest), @@ -62,7 +62,7 @@ class InstanceViewTest(test.BaseAdminViewTests): .AndReturn([servers, False]) api.nova.flavor_list(IsA(http.HttpRequest)). \ AndRaise(self.exceptions.nova) - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True).\ + api.keystone.tenant_list(IsA(http.HttpRequest)).\ AndReturn(tenants) for server in servers: api.nova.flavor_get(IsA(http.HttpRequest), server.flavor["id"]). \ @@ -93,7 +93,7 @@ class InstanceViewTest(test.BaseAdminViewTests): .AndReturn([servers, False]) api.nova.flavor_list(IsA(http.HttpRequest)). \ AndReturn(flavors) - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True).\ + api.keystone.tenant_list(IsA(http.HttpRequest)).\ AndReturn(tenants) for server in servers: api.nova.flavor_get(IsA(http.HttpRequest), server.flavor["id"]). \ @@ -153,7 +153,7 @@ class InstanceViewTest(test.BaseAdminViewTests): @test.create_stubs({api.nova: ('flavor_list', 'server_list',), api.keystone: ('tenant_list',)}) def test_index_options_before_migrate(self): - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True).\ + api.keystone.tenant_list(IsA(http.HttpRequest)).\ AndReturn(self.tenants.list()) search_opts = {'marker': None, 'paginate': True} api.nova.server_list(IsA(http.HttpRequest), @@ -173,8 +173,8 @@ class InstanceViewTest(test.BaseAdminViewTests): def test_index_options_after_migrate(self): server = self.servers.first() server.status = "VERIFY_RESIZE" - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True).\ - AndReturn(self.tenants.list()) + api.keystone.tenant_list(IsA(http.HttpRequest)) \ + .AndReturn(self.tenants.list()) search_opts = {'marker': None, 'paginate': True} api.nova.server_list(IsA(http.HttpRequest), all_tenants=True, search_opts=search_opts) \ diff --git a/openstack_dashboard/dashboards/admin/instances/views.py b/openstack_dashboard/dashboards/admin/instances/views.py index edb0af30c..e8cf27ac9 100644 --- a/openstack_dashboard/dashboards/admin/instances/views.py +++ b/openstack_dashboard/dashboards/admin/instances/views.py @@ -73,7 +73,7 @@ class AdminIndexView(tables.DataTableView): # Gather our tenants to correlate against IDs try: - tenants = api.keystone.tenant_list(self.request, admin=True) + tenants = api.keystone.tenant_list(self.request) except: tenants = [] msg = _('Unable to retrieve instance tenant information.') diff --git a/openstack_dashboard/dashboards/admin/networks/forms.py b/openstack_dashboard/dashboards/admin/networks/forms.py index 13f177e14..6938beb49 100644 --- a/openstack_dashboard/dashboards/admin/networks/forms.py +++ b/openstack_dashboard/dashboards/admin/networks/forms.py @@ -48,7 +48,7 @@ class CreateNetwork(forms.SelfHandlingForm): def __init__(self, request, *args, **kwargs): super(CreateNetwork, self).__init__(request, *args, **kwargs) tenant_choices = [('', _("Select a project"))] - for tenant in api.keystone.tenant_list(request, admin=True): + for tenant in api.keystone.tenant_list(request): if tenant.enabled: tenant_choices.append((tenant.id, tenant.name)) self.fields['tenant_id'].choices = tenant_choices diff --git a/openstack_dashboard/dashboards/admin/networks/tests.py b/openstack_dashboard/dashboards/admin/networks/tests.py index 1c4e45d13..08c9a07fa 100644 --- a/openstack_dashboard/dashboards/admin/networks/tests.py +++ b/openstack_dashboard/dashboards/admin/networks/tests.py @@ -35,7 +35,7 @@ class NetworkTests(test.BaseAdminViewTests): tenants = self.tenants.list() api.quantum.network_list(IsA(http.HttpRequest)) \ .AndReturn(self.networks.list()) - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\ + api.keystone.tenant_list(IsA(http.HttpRequest))\ .AndReturn(tenants) self.mox.ReplayAll() @@ -151,7 +151,7 @@ class NetworkTests(test.BaseAdminViewTests): @test.create_stubs({api.keystone: ('tenant_list',)}) def test_network_create_get(self): tenants = self.tenants.list() - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\ + api.keystone.tenant_list(IsA(http.HttpRequest))\ .AndReturn(tenants) self.mox.ReplayAll() @@ -166,7 +166,7 @@ class NetworkTests(test.BaseAdminViewTests): tenants = self.tenants.list() tenant_id = self.tenants.first().id network = self.networks.first() - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\ + api.keystone.tenant_list(IsA(http.HttpRequest))\ .AndReturn(tenants) params = {'name': network.name, 'tenant_id': tenant_id, @@ -194,7 +194,7 @@ class NetworkTests(test.BaseAdminViewTests): tenants = self.tenants.list() tenant_id = self.tenants.first().id network = self.networks.first() - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\ + api.keystone.tenant_list(IsA(http.HttpRequest))\ .AndReturn(tenants) params = {'name': network.name, 'tenant_id': tenant_id, @@ -301,7 +301,7 @@ class NetworkTests(test.BaseAdminViewTests): def test_delete_network(self): tenants = self.tenants.list() network = self.networks.first() - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\ + api.keystone.tenant_list(IsA(http.HttpRequest))\ .AndReturn(tenants) api.quantum.network_list(IsA(http.HttpRequest))\ .AndReturn([network]) @@ -320,7 +320,7 @@ class NetworkTests(test.BaseAdminViewTests): def test_delete_network_exception(self): tenants = self.tenants.list() network = self.networks.first() - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\ + api.keystone.tenant_list(IsA(http.HttpRequest))\ .AndReturn(tenants) api.quantum.network_list(IsA(http.HttpRequest))\ .AndReturn([network]) diff --git a/openstack_dashboard/dashboards/admin/networks/views.py b/openstack_dashboard/dashboards/admin/networks/views.py index 32f08b14c..e4de670b4 100644 --- a/openstack_dashboard/dashboards/admin/networks/views.py +++ b/openstack_dashboard/dashboards/admin/networks/views.py @@ -42,7 +42,7 @@ class IndexView(tables.DataTableView): def _get_tenant_list(self): if not hasattr(self, "_tenants"): try: - tenants = api.keystone.tenant_list(self.request, admin=True) + tenants = api.keystone.tenant_list(self.request) except: tenants = [] msg = _('Unable to retrieve instance tenant information.') diff --git a/openstack_dashboard/dashboards/admin/overview/tests.py b/openstack_dashboard/dashboards/admin/overview/tests.py index 92a343675..692888513 100644 --- a/openstack_dashboard/dashboards/admin/overview/tests.py +++ b/openstack_dashboard/dashboards/admin/overview/tests.py @@ -45,7 +45,7 @@ class UsageViewTests(test.BaseAdminViewTests): now = timezone.now() usage_obj = api.nova.NovaUsage(self.usages.first()) quota_data = self.quota_usages.first() - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True) \ + api.keystone.tenant_list(IsA(http.HttpRequest)) \ .AndReturn(self.tenants.list()) api.nova.usage_list(IsA(http.HttpRequest), datetime.datetime(now.year, now.month, 1, 0, 0, 0), @@ -77,7 +77,7 @@ class UsageViewTests(test.BaseAdminViewTests): now = timezone.now() usage_obj = api.nova.NovaUsage(self.usages.first()) quota_data = self.quota_usages.first() - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True) \ + api.keystone.tenant_list(IsA(http.HttpRequest)) \ .AndReturn(self.tenants.list()) api.nova.usage_list(IsA(http.HttpRequest), datetime.datetime(now.year, now.month, 1, 0, 0, 0), diff --git a/openstack_dashboard/dashboards/admin/overview/views.py b/openstack_dashboard/dashboards/admin/overview/views.py index 8f7714822..156ea36bb 100644 --- a/openstack_dashboard/dashboards/admin/overview/views.py +++ b/openstack_dashboard/dashboards/admin/overview/views.py @@ -41,7 +41,7 @@ class GlobalOverview(usage.UsageView): data = super(GlobalOverview, self).get_data() # Pre-fill tenant names try: - tenants = api.keystone.tenant_list(self.request, admin=True) + tenants = api.keystone.tenant_list(self.request) except: tenants = [] exceptions.handle(self.request, diff --git a/openstack_dashboard/dashboards/admin/projects/forms.py b/openstack_dashboard/dashboards/admin/projects/forms.py index 9450ac64e..563ae5039 100644 --- a/openstack_dashboard/dashboards/admin/projects/forms.py +++ b/openstack_dashboard/dashboards/admin/projects/forms.py @@ -25,9 +25,9 @@ from openstack_dashboard.dashboards.admin.users.forms import CreateUserForm class CreateUser(CreateUserForm): role_id = forms.ChoiceField(widget=forms.HiddenInput()) - tenant_id = forms.CharField(widget=forms.HiddenInput()) + project = forms.CharField(widget=forms.HiddenInput()) def __init__(self, request, *args, **kwargs): super(CreateUser, self).__init__(request, *args, **kwargs) - tenant_id = self.request.path.split("/")[-1] - self.fields['tenant_id'].initial = tenant_id + project = self.request.path.split("/")[-1] + self.fields['project'].initial = project diff --git a/openstack_dashboard/dashboards/admin/projects/tests.py b/openstack_dashboard/dashboards/admin/projects/tests.py index fd5a45347..c3c81484f 100644 --- a/openstack_dashboard/dashboards/admin/projects/tests.py +++ b/openstack_dashboard/dashboards/admin/projects/tests.py @@ -34,7 +34,7 @@ INDEX_URL = reverse('horizon:admin:projects:index') class TenantsViewTests(test.BaseAdminViewTests): def test_index(self): self.mox.StubOutWithMock(api.keystone, 'tenant_list') - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True) \ + api.keystone.tenant_list(IsA(http.HttpRequest)) \ .AndReturn(self.tenants.list()) self.mox.ReplayAll() @@ -45,7 +45,7 @@ class TenantsViewTests(test.BaseAdminViewTests): class CreateProjectWorkflowTests(test.BaseAdminViewTests): def _get_project_info(self, project): - project_info = {"tenant_name": project.name, + project_info = {"name": project.name, "description": project.description, "enabled": project.enabled} return project_info @@ -146,9 +146,9 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests): ulist = workflow_data["role_" + role.id] for user_id in ulist: api.keystone.add_tenant_user_role(IsA(http.HttpRequest), - tenant_id=self.tenant.id, - user_id=user_id, - role_id=role.id) + project=self.tenant.id, + user=user_id, + role=role.id) nova_updated_quota = dict([(key, quota_data[key]) for key in quotas.NOVA_QUOTA_FIELDS]) @@ -276,9 +276,9 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests): ulist = workflow_data["role_" + role.id] for user_id in ulist: api.keystone.add_tenant_user_role(IsA(http.HttpRequest), - tenant_id=self.tenant.id, - user_id=user_id, - role_id=role.id) + project=self.tenant.id, + user=user_id, + role=role.id) nova_updated_quota = dict([(key, quota_data[key]) for key in quotas.NOVA_QUOTA_FIELDS]) @@ -338,9 +338,9 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests): ulist = workflow_data["role_" + role.id] for user_id in ulist: api.keystone.add_tenant_user_role(IsA(http.HttpRequest), - tenant_id=self.tenant.id, - user_id=user_id, - role_id=role.id) \ + project=self.tenant.id, + user=user_id, + role=role.id) \ .AndRaise(self.exceptions.keystone) break break @@ -506,8 +506,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): quota.metadata_items = 444 quota.volumes = 444 - updated_project = {"tenant_name": project._info["name"], - "tenant_id": project.id, + updated_project = {"name": project._info["name"], "description": project._info["description"], "enabled": project.enabled} updated_quota = self._get_quota_info(quota) @@ -516,12 +515,14 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles) # handle - api.keystone.tenant_update(IsA(http.HttpRequest), **updated_project) \ + api.keystone.tenant_update(IsA(http.HttpRequest), + project.id, + **updated_project) \ .AndReturn(project) api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles) api.keystone.user_list(IsA(http.HttpRequest), - tenant_id=self.tenant.id).AndReturn(users) + project=self.tenant.id).AndReturn(users) # admin user - try to remove all roles on current project, warning api.keystone.roles_for_user(IsA(http.HttpRequest), '1', @@ -534,14 +535,14 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): .AndReturn((roles[0],)) # remove role 1 api.keystone.remove_tenant_user_role(IsA(http.HttpRequest), - tenant_id=self.tenant.id, - user_id='2', - role_id='1') + project=self.tenant.id, + user='2', + role='1') # add role 2 api.keystone.add_tenant_user_role(IsA(http.HttpRequest), - tenant_id=self.tenant.id, - user_id='2', - role_id='2') + project=self.tenant.id, + user='2', + role='2') # member user 3 - has role 2 api.keystone.roles_for_user(IsA(http.HttpRequest), '3', @@ -549,14 +550,14 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): .AndReturn((roles[1],)) # remove role 2 api.keystone.remove_tenant_user_role(IsA(http.HttpRequest), - tenant_id=self.tenant.id, - user_id='3', - role_id='2') + project=self.tenant.id, + user='3', + role='2') # add role 1 api.keystone.add_tenant_user_role(IsA(http.HttpRequest), - tenant_id=self.tenant.id, - user_id='3', - role_id='1') + project=self.tenant.id, + user='3', + role='1') nova_updated_quota = dict([(key, updated_quota[key]) for key in quotas.NOVA_QUOTA_FIELDS]) @@ -647,8 +648,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): quota.metadata_items = 444 quota.volumes = 444 - updated_project = {"tenant_name": project._info["name"], - "tenant_id": project.id, + updated_project = {"name": project._info["name"], "description": project._info["description"], "enabled": project.enabled} updated_quota = self._get_quota_info(quota) @@ -657,7 +657,9 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles) # handle - api.keystone.tenant_update(IsA(http.HttpRequest), **updated_project) \ + api.keystone.tenant_update(IsA(http.HttpRequest), + project.id, + **updated_project) \ .AndRaise(self.exceptions.keystone) self.mox.ReplayAll() @@ -722,8 +724,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): quota[0].limit = 444 quota[1].limit = -1 - updated_project = {"tenant_name": project._info["name"], - "tenant_id": project.id, + updated_project = {"name": project._info["name"], "description": project._info["description"], "enabled": project.enabled} updated_quota = self._get_quota_info(quota) @@ -733,12 +734,14 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): # handle # handle - api.keystone.tenant_update(IsA(http.HttpRequest), **updated_project) \ + api.keystone.tenant_update(IsA(http.HttpRequest), + project.id, + **updated_project) \ .AndReturn(project) api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles) api.keystone.user_list(IsA(http.HttpRequest), - tenant_id=self.tenant.id).AndReturn(users) + project=self.tenant.id).AndReturn(users) # admin user - try to remove all roles on current project, warning api.keystone.roles_for_user(IsA(http.HttpRequest), '1', @@ -756,9 +759,9 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): .AndReturn((roles[0],)) # add role 2 api.keystone.add_tenant_user_role(IsA(http.HttpRequest), - tenant_id=self.tenant.id, - user_id='3', - role_id='2') + project=self.tenant.id, + user='3', + role='2') nova_updated_quota = dict([(key, updated_quota[key]) for key in quotas.NOVA_QUOTA_FIELDS]) @@ -827,8 +830,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): quota.metadata_items = 444 quota.volumes = 444 - updated_project = {"tenant_name": project._info["name"], - "tenant_id": project.id, + updated_project = {"name": project._info["name"], "description": project._info["description"], "enabled": project.enabled} updated_quota = self._get_quota_info(quota) @@ -837,41 +839,40 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests): api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles) # handle - api.keystone.tenant_update(IsA(http.HttpRequest), **updated_project) \ + api.keystone.tenant_update(IsA(http.HttpRequest), + project.id, + **updated_project) \ .AndReturn(project) api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles) api.keystone.user_list(IsA(http.HttpRequest), - tenant_id=self.tenant.id).AndReturn(users) + project=self.tenant.id).AndReturn(users) # admin user - try to remove all roles on current project, warning api.keystone.roles_for_user(IsA(http.HttpRequest), '1', - self.tenant.id) \ - .AndReturn(roles) + self.tenant.id).AndReturn(roles) # member user 1 - has role 1, will remove it api.keystone.roles_for_user(IsA(http.HttpRequest), '2', - self.tenant.id) \ - .AndReturn((roles[1],)) + self.tenant.id).AndReturn((roles[1],)) # member user 3 - has role 2 api.keystone.roles_for_user(IsA(http.HttpRequest), '3', - self.tenant.id) \ - .AndReturn((roles[0],)) + self.tenant.id).AndReturn((roles[0],)) # add role 2 api.keystone.add_tenant_user_role(IsA(http.HttpRequest), - tenant_id=self.tenant.id, - user_id='3', - role_id='2')\ - .AndRaise(self.exceptions.nova) + project=self.tenant.id, + user='3', + role='2')\ + .AndRaise(self.exceptions.keystone) self.mox.ReplayAll() # submit form data project_data = {"name": project._info["name"], - "id": project.id, - "description": project._info["description"], - "enabled": project.enabled} + "id": project.id, + "description": project._info["description"], + "enabled": project.enabled} workflow_data.update(project_data) workflow_data.update(updated_quota) url = reverse('horizon:admin:projects:update', diff --git a/openstack_dashboard/dashboards/admin/projects/views.py b/openstack_dashboard/dashboards/admin/projects/views.py index e87fbf52c..1017c78f9 100644 --- a/openstack_dashboard/dashboards/admin/projects/views.py +++ b/openstack_dashboard/dashboards/admin/projects/views.py @@ -71,7 +71,7 @@ class IndexView(tables.DataTableView): def get_data(self): tenants = [] try: - tenants = api.keystone.tenant_list(self.request, admin=True) + tenants = api.keystone.tenant_list(self.request) except: exceptions.handle(self.request, _("Unable to retrieve project list.")) @@ -183,7 +183,7 @@ class CreateUserView(CreateView): def get_initial(self): default_role = api.keystone.get_default_role(self.request) return {'role_id': getattr(default_role, "id", None), - 'tenant_id': self.kwargs['tenant_id']} + 'project': self.kwargs['tenant_id']} def get_context_data(self, **kwargs): context = super(CreateUserView, self).get_context_data(**kwargs) diff --git a/openstack_dashboard/dashboards/admin/projects/workflows.py b/openstack_dashboard/dashboards/admin/projects/workflows.py index 905a03041..2337da57c 100644 --- a/openstack_dashboard/dashboards/admin/projects/workflows.py +++ b/openstack_dashboard/dashboards/admin/projects/workflows.py @@ -218,7 +218,7 @@ class CreateProject(workflows.Workflow): try: desc = data['description'] self.object = api.keystone.tenant_create(request, - tenant_name=data['name'], + name=data['name'], description=desc, enabled=data['enabled']) except: @@ -242,9 +242,9 @@ class CreateProject(workflows.Workflow): users_added = 0 for user in role_list: api.keystone.add_tenant_user_role(request, - tenant_id=project_id, - user_id=user, - role_id=role.id) + project=project_id, + user=user, + role=role.id) users_added += 1 users_to_add -= users_added except: @@ -300,12 +300,16 @@ class UpdateProject(workflows.Workflow): return message % self.context.get('name', 'unknown project') def handle(self, request, data): + # FIXME(gabriel): This should be refactored to use Python's built-in + # sets and do this all in a single "roles to add" and "roles to remove" + # pass instead of the multi-pass thing happening now. + project_id = data['project_id'] # update project info try: api.keystone.tenant_update(request, - tenant_id=project_id, - tenant_name=data['name'], + project_id, + name=data['name'], description=data['description'], enabled=data['enabled']) except: @@ -315,63 +319,80 @@ class UpdateProject(workflows.Workflow): # update project members users_to_modify = 0 try: + # Get our role options available_roles = api.keystone.role_list(request) + + # Get the users currently associated with this project so we + # can diff against it. project_members = api.keystone.user_list(request, - tenant_id=project_id) + project=project_id) users_to_modify = len(project_members) + for user in project_members: - current_roles = [role for role in - api.keystone.roles_for_user(self.request, - user.id, - project_id)] - effective_roles = [] + # Check if there have been any changes in the roles of + # Existing project members. + current_roles = api.keystone.roles_for_user(self.request, + user.id, + project_id) + current_role_ids = [role.id for role in current_roles] for role in available_roles: - role_list = data["role_" + role.id] - if user.id in role_list: - effective_roles.append(role) - if role not in current_roles: + # Check if the user is in the list of users with this role. + if user.id in data["role_" + role.id]: + # Add it if necessary + if role.id not in current_role_ids: # user role has changed api.keystone.add_tenant_user_role( request, - tenant_id=project_id, - user_id=user.id, - role_id=role.id) + project=project_id, + user=user.id, + role=role.id) else: - # user role is unchanged - current_roles.pop(current_roles.index(role)) - if user.id == request.user.id and \ - project_id == request.user.tenant_id and \ - any(x.name == 'admin' for x in current_roles): - # Cannot remove "admin" role on current(admin) project - msg = _('You cannot remove the "admin" role from the ' - 'project you are currently logged into. Please ' - 'switch to another project with admin permissions ' - 'or remove the role manually via the CLI') - messages.warning(request, msg) + # User role is unchanged, so remove it from the + # remaining roles list to avoid removing it later. + index = current_role_ids.index(role.id) + current_role_ids.pop(index) + + # Prevent admins from doing stupid things to themselves. + is_current_user = user.id == request.user.id + is_current_project = project_id == request.user.tenant_id + admin_roles = [role for role in current_roles + if role.name.lower() == 'admin'] + if len(admin_roles): + removing_admin = any([role.id in current_role_ids + for role in admin_roles]) else: - # delete user's removed roles - for to_delete in current_roles: + removing_admin = False + if is_current_user and is_current_project and removing_admin: + # Cannot remove "admin" role on current(admin) project + msg = _('You cannot revoke your administrative privileges ' + 'from the project you are currently logged into. ' + 'Please switch to another project with ' + 'administrative privileges or remove the ' + 'administrative role manually via the CLI.') + messages.warning(request, msg) + + # Otherwise go through and revoke any removed roles. + else: + for id_to_delete in current_role_ids: api.keystone.remove_tenant_user_role( request, - tenant_id=project_id, - user_id=user.id, - role_id=to_delete.id) + project=project_id, + user=user.id, + role=id_to_delete) users_to_modify -= 1 - # add new roles to project + # Grant new roles on the project. for role in available_roles: - # count how many users may be added for exception handling - role_list = data["role_" + role.id] - users_to_modify += len(role_list) + # Count how many users may be added for exception handling. + users_to_modify += len(data["role_" + role.id]) for role in available_roles: - role_list = data["role_" + role.id] users_added = 0 - for user_id in role_list: + for user_id in data["role_" + role.id]: if not filter(lambda x: user_id == x.id, project_members): api.keystone.add_tenant_user_role(request, - tenant_id=project_id, - user_id=user_id, - role_id=role.id) + project=project_id, + user=user_id, + role=role.id) users_added += 1 users_to_modify -= users_added except: diff --git a/openstack_dashboard/dashboards/admin/routers/tests.py b/openstack_dashboard/dashboards/admin/routers/tests.py index 88af19924..ef2222d8a 100644 --- a/openstack_dashboard/dashboards/admin/routers/tests.py +++ b/openstack_dashboard/dashboards/admin/routers/tests.py @@ -34,8 +34,7 @@ class RouterTests(test.BaseAdminViewTests, r_test.RouterTests): api.quantum.router_list( IsA(http.HttpRequest), search_opts=None).AndReturn(self.routers.list()) - api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\ - .AndReturn(tenants) + api.keystone.tenant_list(IsA(http.HttpRequest)).AndReturn(tenants) self._mock_external_network_list() self.mox.ReplayAll() @@ -49,7 +48,6 @@ class RouterTests(test.BaseAdminViewTests, r_test.RouterTests): @test.create_stubs({api.quantum: ('router_list',), api.keystone: ('tenant_list',)}) def test_index_router_list_exception(self): - tenants = self.tenants.list() api.quantum.router_list( IsA(http.HttpRequest), search_opts=None).AndRaise(self.exceptions.quantum) diff --git a/openstack_dashboard/dashboards/admin/users/forms.py b/openstack_dashboard/dashboards/admin/users/forms.py index 41e040935..ff972f4ec 100644 --- a/openstack_dashboard/dashboards/admin/users/forms.py +++ b/openstack_dashboard/dashboards/admin/users/forms.py @@ -21,7 +21,6 @@ import logging from django.forms import ValidationError -from django.utils.encoding import force_unicode from django.utils.translation import ugettext_lazy as _ from django.views.decorators.debug import sensitive_variables @@ -31,7 +30,6 @@ from horizon import messages from horizon.utils import validators from openstack_dashboard import api -from django.contrib.auth import logout LOG = logging.getLogger(__name__) @@ -40,13 +38,13 @@ LOG = logging.getLogger(__name__) class BaseUserForm(forms.SelfHandlingForm): def __init__(self, request, *args, **kwargs): super(BaseUserForm, self).__init__(request, *args, **kwargs) - # Populate tenant choices - tenant_choices = [('', _("Select a project"))] + # Populate project choices + project_choices = [('', _("Select a project"))] - for tenant in api.keystone.tenant_list(request, admin=True): - if tenant.enabled: - tenant_choices.append((tenant.id, tenant.name)) - self.fields['tenant_id'].choices = tenant_choices + for project in api.keystone.tenant_list(request): + if project.enabled: + project_choices.append((project.id, project.name)) + self.fields['project'].choices = project_choices def clean(self): '''Check to make sure password fields match.''' @@ -64,16 +62,16 @@ class CreateUserForm(BaseUserForm): name = forms.CharField(label=_("User Name")) email = forms.EmailField(label=_("Email")) password = forms.RegexField( - label=_("Password"), - widget=forms.PasswordInput(render_value=False), - regex=validators.password_validator(), - error_messages={'invalid': validators.password_validator_msg()}) + 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"), - required=False, - widget=forms.PasswordInput(render_value=False)) - tenant_id = forms.DynamicChoiceField(label=_("Primary Project"), - add_item_link=ADD_PROJECT_URL) + label=_("Confirm Password"), + required=False, + widget=forms.PasswordInput(render_value=False)) + project = forms.DynamicChoiceField(label=_("Primary Project"), + add_item_link=ADD_PROJECT_URL) role_id = forms.ChoiceField(label=_("Role")) def __init__(self, *args, **kwargs): @@ -92,7 +90,7 @@ class CreateUserForm(BaseUserForm): data['name'], data['email'], data['password'], - data['tenant_id'], + data['project'], True) messages.success(request, _('User "%s" was successfully created.') @@ -100,9 +98,9 @@ class CreateUserForm(BaseUserForm): if data['role_id']: try: api.keystone.add_tenant_user_role(request, - data['tenant_id'], - new_user.id, - data['role_id']) + data['project'], + new_user.id, + data['role_id']) except: exceptions.handle(request, _('Unable to add user' @@ -116,17 +114,17 @@ class UpdateUserForm(BaseUserForm): id = forms.CharField(label=_("ID"), widget=forms.HiddenInput) name = forms.CharField(label=_("User Name")) email = forms.EmailField(label=_("Email")) - password = forms.RegexField(label=_("Password"), - widget=forms.PasswordInput(render_value=False), - regex=validators.password_validator(), - required=False, - error_messages={'invalid': - validators.password_validator_msg()}) + password = forms.RegexField( + label=_("Password"), + widget=forms.PasswordInput(render_value=False), + regex=validators.password_validator(), + required=False, + error_messages={'invalid': validators.password_validator_msg()}) confirm_password = forms.CharField( - label=_("Confirm Password"), - widget=forms.PasswordInput(render_value=False), - required=False) - tenant_id = forms.ChoiceField(label=_("Primary Project")) + label=_("Confirm Password"), + widget=forms.PasswordInput(render_value=False), + required=False) + project = forms.ChoiceField(label=_("Primary Project")) def __init__(self, request, *args, **kwargs): super(UpdateUserForm, self).__init__(request, *args, **kwargs) @@ -139,62 +137,16 @@ class UpdateUserForm(BaseUserForm): # password and confirm_password strings. @sensitive_variables('data', 'password') def handle(self, request, data): - failed, succeeded = [], [] - user_is_editable = api.keystone.keystone_can_edit_user() user = data.pop('id') - tenant = data.pop('tenant_id') - if user_is_editable: - password = data.pop('password') - data.pop('confirm_password', None) + # Throw away the password confirmation, we're done with it. + data.pop('confirm_password', None) - if user_is_editable: - # Update user details - msg_bits = (_('name'), _('email')) - try: - api.keystone.user_update(request, user, **data) - succeeded.extend(msg_bits) - except: - failed.extend(msg_bits) - exceptions.handle(request, ignore=True) - - # Update default tenant - msg_bits = (_('primary project'),) try: - api.keystone.user_update_tenant(request, user, tenant) - succeeded.extend(msg_bits) + api.keystone.user_update(request, user, **data) + messages.success(request, + _('User has been updated successfully.')) except: - failed.append(msg_bits) exceptions.handle(request, ignore=True) - - # Check for existing roles - # Show a warning if no role exists for the tenant - user_roles = api.keystone.roles_for_user(request, user, tenant) - if not user_roles: - messages.warning(request, - _('The user %s has no role defined for' + - ' that project.') - % data.get('name', None)) - - if user_is_editable: - # If present, update password - # FIXME(gabriel): password change should be its own form and view - if password: - msg_bits = (_('password'),) - try: - api.keystone.user_update_password(request, user, password) - succeeded.extend(msg_bits) - if user == request.user.id: - logout(request) - except: - failed.extend(msg_bits) - exceptions.handle(request, ignore=True) - - if succeeded: - messages.success(request, _('User has been updated successfully.')) - if failed: - failed = map(force_unicode, failed) - messages.error(request, - _('Unable to update %(attributes)s for the user.') - % {"attributes": ", ".join(failed)}) + messages.error(request, _('Unable to update the user.')) return True diff --git a/openstack_dashboard/dashboards/admin/users/tests.py b/openstack_dashboard/dashboards/admin/users/tests.py index 1586a8cf5..d6fb5bd35 100644 --- a/openstack_dashboard/dashboards/admin/users/tests.py +++ b/openstack_dashboard/dashboards/admin/users/tests.py @@ -55,8 +55,7 @@ class UsersViewTests(test.BaseAdminViewTests): user = self.users.get(id="1") role = self.roles.first() - api.keystone.tenant_list(IgnoreArg(), admin=True) \ - .AndReturn(self.tenants.list()) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) api.keystone.user_create(IgnoreArg(), user.name, user.email, @@ -74,7 +73,7 @@ class UsersViewTests(test.BaseAdminViewTests): 'name': user.name, 'email': user.email, 'password': user.password, - 'tenant_id': self.tenant.id, + 'project': self.tenant.id, 'role_id': self.roles.first().id, 'confirm_password': user.password} res = self.client.post(USER_CREATE_URL, formData) @@ -88,8 +87,7 @@ class UsersViewTests(test.BaseAdminViewTests): def test_create_with_password_mismatch(self): user = self.users.get(id="1") - api.keystone.tenant_list(IgnoreArg(), admin=True) \ - .AndReturn(self.tenants.list()) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list()) api.keystone.get_default_role(IgnoreArg()) \ .AndReturn(self.roles.first()) @@ -100,7 +98,7 @@ class UsersViewTests(test.BaseAdminViewTests): 'name': user.name, 'email': user.email, 'password': user.password, - 'tenant_id': self.tenant.id, + 'project': self.tenant.id, 'role_id': self.roles.first().id, 'confirm_password': "doesntmatch"} @@ -114,8 +112,7 @@ class UsersViewTests(test.BaseAdminViewTests): def test_create_validation_for_password_too_short(self): user = self.users.get(id="1") - api.keystone.tenant_list(IgnoreArg(), admin=True) \ - .AndReturn(self.tenants.list()) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list()) api.keystone.get_default_role(IgnoreArg()) \ .AndReturn(self.roles.first()) @@ -127,7 +124,7 @@ class UsersViewTests(test.BaseAdminViewTests): 'name': user.name, 'email': user.email, 'password': 'four', - 'tenant_id': self.tenant.id, + 'project': self.tenant.id, 'role_id': self.roles.first().id, 'confirm_password': 'four'} @@ -143,8 +140,7 @@ class UsersViewTests(test.BaseAdminViewTests): def test_create_validation_for_password_too_long(self): user = self.users.get(id="1") - api.keystone.tenant_list(IgnoreArg(), admin=True) \ - .AndReturn(self.tenants.list()) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list()) api.keystone.get_default_role(IgnoreArg()) \ .AndReturn(self.roles.first()) @@ -156,7 +152,7 @@ class UsersViewTests(test.BaseAdminViewTests): 'name': user.name, 'email': user.email, 'password': 'MoreThanEighteenChars', - 'tenant_id': self.tenant.id, + 'project': self.tenant.id, 'role_id': self.roles.first().id, 'confirm_password': 'MoreThanEighteenChars'} @@ -174,24 +170,17 @@ class UsersViewTests(test.BaseAdminViewTests): 'roles_for_user', )}) def test_update(self): user = self.users.get(id="1") + test_password = 'normalpwd' api.keystone.user_get(IsA(http.HttpRequest), '1', - admin=True).AndReturn(user) - api.keystone.tenant_list(IgnoreArg(), - admin=True).AndReturn(self.tenants.list()) + admin=True).AndReturn(user) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) api.keystone.user_update(IsA(http.HttpRequest), user.id, email=u'test@example.com', - name=u'test_user').AndReturn(None) - api.keystone.user_update_tenant(IsA(http.HttpRequest), - user.id, - self.tenant.id).AndReturn(None) - api.keystone.roles_for_user(IsA(http.HttpRequest), - user.id, - self.tenant.id).AndReturn(None) - api.keystone.user_update_password(IsA(http.HttpRequest), - user.id, - IgnoreArg()).AndReturn(None) + name=u'test_user', + password=test_password, + project=self.tenant.id).AndReturn(None) self.mox.ReplayAll() @@ -199,14 +188,13 @@ class UsersViewTests(test.BaseAdminViewTests): 'id': user.id, 'name': user.name, 'email': user.email, - 'password': 'normalpwd', - 'tenant_id': self.tenant.id, - 'confirm_password': 'normalpwd'} + 'password': test_password, + 'project': self.tenant.id, + 'confirm_password': test_password} res = self.client.post(USER_UPDATE_URL, formData) self.assertNoFormErrors(res) - self.assertMessageCount(warning=1) @test.create_stubs({api.keystone: ('user_get', 'tenant_list', @@ -219,28 +207,21 @@ class UsersViewTests(test.BaseAdminViewTests): api.keystone.user_get(IsA(http.HttpRequest), '1', admin=True).AndReturn(user) - api.keystone.tenant_list(IgnoreArg(), admin=True) \ - .AndReturn(self.tenants.list()) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) api.keystone.keystone_can_edit_user().AndReturn(False) api.keystone.keystone_can_edit_user().AndReturn(False) - api.keystone.user_update_tenant(IsA(http.HttpRequest), - user.id, - self.tenant.id).AndReturn(None) - api.keystone.roles_for_user(IsA(http.HttpRequest), - user.id, - self.tenant.id).AndReturn(None) self.mox.ReplayAll() formData = {'method': 'UpdateUserForm', 'id': user.id, 'name': user.name, - 'tenant_id': self.tenant.id, } + 'project': self.tenant.id, } res = self.client.post(USER_UPDATE_URL, formData) self.assertNoFormErrors(res) - self.assertMessageCount(warning=1) + self.assertMessageCount(error=1) @test.create_stubs({api.keystone: ('user_get', 'tenant_list')}) def test_update_validation_for_password_too_short(self): @@ -248,8 +229,7 @@ class UsersViewTests(test.BaseAdminViewTests): api.keystone.user_get(IsA(http.HttpRequest), '1', admin=True).AndReturn(user) - api.keystone.tenant_list(IgnoreArg(), - admin=True).AndReturn(self.tenants.list()) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) self.mox.ReplayAll() @@ -258,7 +238,7 @@ class UsersViewTests(test.BaseAdminViewTests): 'name': user.name, 'email': user.email, 'password': 't', - 'tenant_id': self.tenant.id, + 'project': self.tenant.id, 'confirm_password': 't'} res = self.client.post(USER_UPDATE_URL, formData) @@ -273,8 +253,7 @@ class UsersViewTests(test.BaseAdminViewTests): api.keystone.user_get(IsA(http.HttpRequest), '1', admin=True).AndReturn(user) - api.keystone.tenant_list(IgnoreArg(), - admin=True).AndReturn(self.tenants.list()) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) self.mox.ReplayAll() @@ -283,7 +262,7 @@ class UsersViewTests(test.BaseAdminViewTests): 'name': user.name, 'email': user.email, 'password': 'ThisIsASuperLongPassword', - 'tenant_id': self.tenant.id, + 'project': self.tenant.id, 'confirm_password': 'ThisIsASuperLongPassword'} res = self.client.post(USER_UPDATE_URL, formData) @@ -374,8 +353,7 @@ class SeleniumTests(test.SeleniumAdminTestCase): 'role_list', 'user_list')}) def test_modal_create_user_with_passwords_not_matching(self): - api.keystone.tenant_list(IgnoreArg(), admin=True) \ - .AndReturn(self.tenants.list()) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list()) api.keystone.user_list(IgnoreArg()).AndReturn(self.users.list()) api.keystone.get_default_role(IgnoreArg()) \ @@ -406,8 +384,7 @@ class SeleniumTests(test.SeleniumAdminTestCase): def test_update_user_with_passwords_not_matching(self): api.keystone.user_get(IsA(http.HttpRequest), '1', admin=True).AndReturn(self.user) - api.keystone.tenant_list(IgnoreArg(), admin=True) \ - .AndReturn(self.tenants.list()) + api.keystone.tenant_list(IgnoreArg()).AndReturn(self.tenants.list()) self.mox.ReplayAll() self.selenium.get("%s%s" % (self.live_server_url, USER_UPDATE_URL)) diff --git a/openstack_dashboard/dashboards/admin/users/views.py b/openstack_dashboard/dashboards/admin/users/views.py index 11562e797..5856e2a4e 100644 --- a/openstack_dashboard/dashboards/admin/users/views.py +++ b/openstack_dashboard/dashboards/admin/users/views.py @@ -80,7 +80,7 @@ class UpdateView(forms.ModalFormView): user = self.get_object() return {'id': user.id, 'name': user.name, - 'tenant_id': getattr(user, 'tenantId', None), + 'project': user.project_id, 'email': user.email} diff --git a/openstack_dashboard/dashboards/admin/volumes/tests.py b/openstack_dashboard/dashboards/admin/volumes/tests.py index 10741888b..20aac21c1 100644 --- a/openstack_dashboard/dashboards/admin/volumes/tests.py +++ b/openstack_dashboard/dashboards/admin/volumes/tests.py @@ -36,8 +36,8 @@ class VolumeTests(test.BaseAdminViewTests): AndReturn([self.servers.list(), False]) cinder.volume_type_list(IsA(http.HttpRequest)).\ AndReturn(self.volume_types.list()) - keystone.tenant_list(IsA(http.HttpRequest), - admin=True).AndReturn(self.tenants.list()) + keystone.tenant_list(IsA(http.HttpRequest)) \ + .AndReturn(self.tenants.list()) self.mox.ReplayAll() @@ -80,8 +80,8 @@ class VolumeTests(test.BaseAdminViewTests): AndReturn(self.volume_types.list()) cinder.volume_type_delete(IsA(http.HttpRequest), str(volume_type.id)) - keystone.tenant_list(IsA(http.HttpRequest), - admin=True).AndReturn(self.tenants.list()) + keystone.tenant_list(IsA(http.HttpRequest)) \ + .AndReturn(self.tenants.list()) self.mox.ReplayAll() res = self.client.post(reverse('horizon:admin:volumes:index'), diff --git a/openstack_dashboard/dashboards/admin/volumes/views.py b/openstack_dashboard/dashboards/admin/volumes/views.py index 1041875c1..58ffdfd2d 100644 --- a/openstack_dashboard/dashboards/admin/volumes/views.py +++ b/openstack_dashboard/dashboards/admin/volumes/views.py @@ -45,7 +45,7 @@ class IndexView(tables.MultiTableView, VolumeTableMixIn): # Gather our tenants to correlate against IDs try: - tenants = keystone.tenant_list(self.request, admin=True) + tenants = keystone.tenant_list(self.request) except: tenants = [] msg = _('Unable to retrieve volume tenant information.') diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index ba29e0a56..1e357e12a 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -26,6 +26,15 @@ TEMPLATE_DEBUG = DEBUG #CSRF_COOKIE_SECURE = True #SESSION_COOKIE_SECURE = True +# Overrides for OpenStack API versions. Use this setting to force the +# OpenStack dashboard to use a specfic API version for a given service API. +# NOTE: The version should be formatted as it appears in the URL for the +# service API. For example, The identity service APIs have inconsistent +# use of the decimal point, so valid options would be "2.0" or "3". +# OPENSTACK_API_VERSIONS = { +# "identity": 3 +# } + # Default OpenStack Dashboard configuration. HORIZON_CONFIG = { 'dashboards': ('project', 'admin', 'settings',), diff --git a/openstack_dashboard/test/api_tests/keystone_tests.py b/openstack_dashboard/test/api_tests/keystone_tests.py index e64827890..de8aafe93 100644 --- a/openstack_dashboard/test/api_tests/keystone_tests.py +++ b/openstack_dashboard/test/api_tests/keystone_tests.py @@ -66,9 +66,11 @@ class RoleAPITests(test.APITestCase): keystoneclient.roles.roles_for_user(self.user.id, tenant.id).AndReturn(self.roles) for role in self.roles: - keystoneclient.roles.remove_user_role(self.user.id, - role.id, - tenant.id) + keystoneclient.roles.revoke(role.id, + domain=None, + group=None, + project=tenant.id, + user=self.user.id) self.mox.ReplayAll() api.keystone.remove_tenant_user(self.request, tenant.id, self.user.id) diff --git a/openstack_dashboard/test/test_data/keystone_data.py b/openstack_dashboard/test/test_data/keystone_data.py index 03d89ba3f..ea61f8188 100644 --- a/openstack_dashboard/test/test_data/keystone_data.py +++ b/openstack_dashboard/test/test_data/keystone_data.py @@ -108,6 +108,7 @@ def data(TEST): 'email': 'test@example.com', 'password': 'password', 'token': 'test_token', + 'project_id': '1', 'enabled': True} user = users.User(users.UserManager(None), user_dict) user_dict = {'id': "2", @@ -115,6 +116,7 @@ def data(TEST): 'email': 'two@example.com', 'password': 'password', 'token': 'test_token', + 'project_id': '1', 'enabled': True} user2 = users.User(users.UserManager(None), user_dict) user_dict = {'id': "3", @@ -122,6 +124,7 @@ def data(TEST): 'email': 'three@example.com', 'password': 'password', 'token': 'test_token', + 'project_id': '1', 'enabled': True} user3 = users.User(users.UserManager(None), user_dict) TEST.users.add(user, user2, user3)