541 lines
18 KiB
Python
541 lines
18 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2012 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# All Rights Reserved.
|
|
#
|
|
# Copyright 2012 Openstack, LLC
|
|
# 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 logging
|
|
import urlparse
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth import logout
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
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
|
|
|
|
|
|
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']
|
|
|
|
def __init__(self, service, *args, **kwargs):
|
|
super(Service, self).__init__(service, *args, **kwargs)
|
|
self.url = service['endpoints'][0]['internalURL']
|
|
self.host = urlparse.urlparse(self.url).hostname
|
|
self.region = service['endpoints'][0]['region']
|
|
self.disabled = None
|
|
|
|
def __unicode__(self):
|
|
if(self.type == "identity"):
|
|
return _("%(type)s (%(backend)s backend)") \
|
|
% {"type": self.type, "backend": keystone_backend_name()}
|
|
else:
|
|
return self.type
|
|
|
|
def __repr__(self):
|
|
return "<Service: %s>" % unicode(self)
|
|
|
|
|
|
def _get_endpoint_url(request, endpoint_type, catalog=None):
|
|
if getattr(request.user, "service_catalog", None):
|
|
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(gabriel): 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):
|
|
"""Returns a client connected to the Keystone backend.
|
|
|
|
Several forms of authentication are supported:
|
|
|
|
* Username + password -> Unscoped authentication
|
|
* Username + password + tenant id -> Scoped authentication
|
|
* Unscoped token -> Unscoped authentication
|
|
* Unscoped token + tenant id -> Scoped authentication
|
|
* Scoped token -> Scoped authentication
|
|
|
|
Available services and data from the backend will vary depending on
|
|
whether the authentication was scoped or unscoped.
|
|
|
|
Lazy authentication if an ``endpoint`` parameter is provided.
|
|
|
|
Calls requiring the admin endpoint should have ``admin=True`` passed in
|
|
as a keyword argument.
|
|
|
|
The client is cached so that subsequent API calls during the same
|
|
request/response cycle don't have to be re-authenticated.
|
|
"""
|
|
user = request.user
|
|
if admin:
|
|
if not user.is_superuser:
|
|
raise exceptions.NotAuthorized
|
|
endpoint_type = 'adminURL'
|
|
else:
|
|
endpoint_type = getattr(settings,
|
|
'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
|
|
if hasattr(request, cache_attr) and (not user.token.id
|
|
or getattr(request, cache_attr).auth_token == user.token.id):
|
|
LOG.debug("Using cached client for token: %s" % user.token.id)
|
|
conn = getattr(request, cache_attr)
|
|
else:
|
|
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)
|
|
remote_addr = request.environ.get('REMOTE_ADDR', '')
|
|
conn = api_version['client'].Client(token=user.token.id,
|
|
endpoint=endpoint,
|
|
original_ip=remote_addr,
|
|
insecure=insecure,
|
|
auth_url=endpoint,
|
|
debug=settings.DEBUG)
|
|
setattr(request, cache_attr, conn)
|
|
return conn
|
|
|
|
|
|
def domain_create(request, name, description=None, enabled=None):
|
|
manager = keystoneclient(request, admin=True).domains
|
|
return manager.create(name,
|
|
description=description,
|
|
enabled=enabled)
|
|
|
|
|
|
def domain_get(request, domain_id):
|
|
manager = keystoneclient(request, admin=True).domains
|
|
return manager.get(domain_id)
|
|
|
|
|
|
def domain_delete(request, domain_id):
|
|
manager = keystoneclient(request, admin=True).domains
|
|
return manager.delete(domain_id)
|
|
|
|
|
|
def domain_list(request):
|
|
manager = keystoneclient(request, admin=True).domains
|
|
return manager.list()
|
|
|
|
|
|
def domain_update(request, domain_id, name=None, description=None,
|
|
enabled=None):
|
|
manager = keystoneclient(request, admin=True).domains
|
|
return manager.update(domain_id, name, description, enabled)
|
|
|
|
|
|
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:
|
|
return manager.create(name, domain,
|
|
description=description,
|
|
enabled=enabled)
|
|
|
|
|
|
# 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 tenant_delete(request, project):
|
|
manager = VERSIONS.get_project_manager(request, admin=True)
|
|
return manager.delete(project)
|
|
|
|
|
|
def tenant_list(request, paginate=False, marker=None, domain=None, user=None):
|
|
manager = VERSIONS.get_project_manager(request, admin=True)
|
|
page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 20)
|
|
limit = None
|
|
if paginate:
|
|
limit = page_size + 1
|
|
|
|
has_more_data = False
|
|
if VERSIONS.active < 3:
|
|
tenants = manager.list(limit, marker)
|
|
if paginate and len(tenants) > page_size:
|
|
tenants.pop(-1)
|
|
has_more_data = True
|
|
else:
|
|
tenants = manager.list(domain=domain, user=user)
|
|
return (tenants, has_more_data)
|
|
|
|
|
|
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):
|
|
return keystoneclient(request, admin=True).users.delete(user_id)
|
|
|
|
|
|
def user_get(request, user_id, admin=True):
|
|
user = keystoneclient(request, admin=admin).users.get(user_id)
|
|
return VERSIONS.upgrade_v2_user(user)
|
|
|
|
|
|
def user_update(request, 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:
|
|
if not data['password']:
|
|
data.pop('password')
|
|
user = manager.update(user, **data)
|
|
|
|
return VERSIONS.upgrade_v2_user(user)
|
|
|
|
|
|
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, 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_own_password(request, origpassword, password):
|
|
client = keystoneclient(request, admin=False)
|
|
if VERSIONS.active < 3:
|
|
client.user_id = request.user.id
|
|
return client.users.update_own_password(origpassword, password)
|
|
else:
|
|
return client.users.update(request.user.id, password=password)
|
|
|
|
|
|
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 group_create(request, domain_id, name, description=None):
|
|
manager = keystoneclient(request, admin=True).groups
|
|
return manager.create(domain=domain_id,
|
|
name=name,
|
|
description=description)
|
|
|
|
|
|
def group_get(request, group_id, admin=True):
|
|
manager = keystoneclient(request, admin=admin).groups
|
|
return manager.get(group_id)
|
|
|
|
|
|
def group_delete(request, group_id):
|
|
manager = keystoneclient(request, admin=True).groups
|
|
return manager.delete(group_id)
|
|
|
|
|
|
def group_list(request):
|
|
manager = keystoneclient(request, admin=True).groups
|
|
return manager.list()
|
|
|
|
|
|
def group_update(request, group_id, name=None, description=None):
|
|
manager = keystoneclient(request, admin=True).groups
|
|
return manager.update(group=group_id,
|
|
name=name,
|
|
description=description)
|
|
|
|
|
|
def add_group_user(request, group_id, user_id):
|
|
manager = keystoneclient(request, admin=True).users
|
|
return manager.add_to_group(group=group_id, user=user_id)
|
|
|
|
|
|
def remove_group_user(request, group_id, user_id):
|
|
manager = keystoneclient(request, admin=True).users
|
|
return manager.remove_from_group(group=group_id, user=user_id)
|
|
|
|
|
|
def role_create(request, name):
|
|
manager = keystoneclient(request, admin=True).roles
|
|
return manager.create(name)
|
|
|
|
|
|
def role_get(request, role_id):
|
|
manager = keystoneclient(request, admin=True).roles
|
|
return manager.get(role_id)
|
|
|
|
|
|
def role_update(request, role_id, name=None):
|
|
manager = keystoneclient(request, admin=True).roles
|
|
return manager.update(role_id, name)
|
|
|
|
|
|
def role_delete(request, role_id):
|
|
manager = keystoneclient(request, admin=True).roles
|
|
return manager.delete(role_id)
|
|
|
|
|
|
def role_list(request):
|
|
""" Returns a global list of available roles. """
|
|
return keystoneclient(request, admin=True).roles.list()
|
|
|
|
|
|
def roles_for_user(request, 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, project=None, user=None, role=None,
|
|
group=None, domain=None):
|
|
""" Adds a role for a user on a tenant. """
|
|
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, project=None, user=None, role=None,
|
|
group=None, domain=None):
|
|
""" Removes a given single role for a user from a tenant. """
|
|
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, 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, project)
|
|
for role in roles:
|
|
remove_tenant_user_role(request, user=user, role=role.id,
|
|
project=project, domain=domain)
|
|
|
|
|
|
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, admin=True).roles.list()
|
|
except:
|
|
roles = []
|
|
exceptions.handle(request)
|
|
for role in roles:
|
|
if role.id == default or role.name == default:
|
|
DEFAULT_ROLE = role
|
|
break
|
|
return DEFAULT_ROLE
|
|
|
|
|
|
def list_ec2_credentials(request, user_id):
|
|
return keystoneclient(request).ec2.list(user_id)
|
|
|
|
|
|
def create_ec2_credentials(request, user_id, tenant_id):
|
|
return keystoneclient(request).ec2.create(user_id, tenant_id)
|
|
|
|
|
|
def get_user_ec2_credentials(request, user_id, access_token):
|
|
return keystoneclient(request).ec2.get(user_id, access_token)
|
|
|
|
|
|
def keystone_can_edit_domain():
|
|
backend_settings = getattr(settings, "OPENSTACK_KEYSTONE_BACKEND", {})
|
|
return backend_settings.get('can_edit_domain', True)
|
|
|
|
|
|
def keystone_can_edit_user():
|
|
backend_settings = getattr(settings, "OPENSTACK_KEYSTONE_BACKEND", {})
|
|
return backend_settings.get('can_edit_user', True)
|
|
|
|
|
|
def keystone_can_edit_project():
|
|
backend_settings = getattr(settings, "OPENSTACK_KEYSTONE_BACKEND", {})
|
|
return backend_settings.get('can_edit_project', True)
|
|
|
|
|
|
def keystone_can_edit_group():
|
|
backend_settings = getattr(settings, "OPENSTACK_KEYSTONE_BACKEND", {})
|
|
return backend_settings.get('can_edit_group', True)
|
|
|
|
|
|
def keystone_can_edit_role():
|
|
backend_settings = getattr(settings, "OPENSTACK_KEYSTONE_BACKEND", {})
|
|
return backend_settings.get('can_edit_role', True)
|
|
|
|
|
|
def keystone_backend_name():
|
|
if hasattr(settings, "OPENSTACK_KEYSTONE_BACKEND"):
|
|
return settings.OPENSTACK_KEYSTONE_BACKEND['name']
|
|
else:
|
|
return 'unknown'
|