summaryrefslogtreecommitdiffstats
path: root/keystone/identity
diff options
context:
space:
mode:
authorAdam Young <ayoung@redhat.com>2013-02-01 11:18:16 -0500
committerAdam Young <ayoung@redhat.com>2013-02-18 15:11:43 -0500
commitb20302aa3e08421295140576d0aeea2fa9e34188 (patch)
tree50459bb43b70c4ae82cf3fc6d5228c9ba1dc4dbf /keystone/identity
parentb1bfca2501ad11a861c9064b97b7fa06fc6d958e (diff)
downloadkeystone-b20302aa3e08421295140576d0aeea2fa9e34188.tar.gz
keystone-b20302aa3e08421295140576d0aeea2fa9e34188.tar.xz
keystone-b20302aa3e08421295140576d0aeea2fa9e34188.zip
project membership to role conversion
Changes the relationship between users and projects. There is no more direct membership in projects. Instead, all membership is now done via roles. A default role has been created called _member_ with a uuid (both configurable) that will be added in place of the group membership for databse upgrades. DocImpact: https://bugs.launchpad.net/openstack-manuals/+bug/1087483 Change-Id: I2482f9ef7b838e5dade5096d6d00e81db71604d1
Diffstat (limited to 'keystone/identity')
-rw-r--r--keystone/identity/backends/kvs.py35
-rw-r--r--keystone/identity/backends/ldap/core.py27
-rw-r--r--keystone/identity/backends/sql.py110
-rw-r--r--keystone/identity/controllers.py10
-rw-r--r--keystone/identity/core.py20
5 files changed, 73 insertions, 129 deletions
diff --git a/keystone/identity/backends/kvs.py b/keystone/identity/backends/kvs.py
index 1d8ce608..6c59fa03 100644
--- a/keystone/identity/backends/kvs.py
+++ b/keystone/identity/backends/kvs.py
@@ -134,24 +134,6 @@ class Identity(kvs.Base, identity.Driver):
role_ids = self.db.get('role_list', [])
return [self.get_role(x) for x in role_ids]
- # These should probably be part of the high-level API
- def add_user_to_project(self, tenant_id, user_id):
- self.get_project(tenant_id)
- user_ref = self._get_user(user_id)
- tenants = set(user_ref.get('tenants', []))
- tenants.add(tenant_id)
- self.update_user(user_id, {'tenants': list(tenants)})
-
- def remove_user_from_project(self, tenant_id, user_id):
- self.get_project(tenant_id)
- user_ref = self._get_user(user_id)
- tenants = set(user_ref.get('tenants', []))
- try:
- tenants.remove(tenant_id)
- except KeyError:
- raise exception.NotFound('User not found in tenant')
- self.update_user(user_id, {'tenants': list(tenants)})
-
def get_projects_for_user(self, user_id):
user_ref = self._get_user(user_id)
return user_ref.get('tenants', [])
@@ -194,7 +176,16 @@ class Identity(kvs.Base, identity.Driver):
roles.remove(role_id)
metadata_ref['roles'] = list(roles)
- self.update_metadata(user_id, tenant_id, metadata_ref)
+
+ if not len(roles):
+ self.db.delete('metadata-%s-%s' % (tenant_id, user_id))
+ user_ref = self._get_user(user_id)
+ tenants = set(user_ref.get('tenants', []))
+ tenants.remove(tenant_id)
+ user_ref['tenants'] = list(tenants)
+ self.update_user(user_id, user_ref)
+ else:
+ self.update_metadata(user_id, tenant_id, metadata_ref)
# CRUD
def create_user(self, user_id, user):
@@ -360,6 +351,12 @@ class Identity(kvs.Base, identity.Driver):
if user_id:
if tenant_id:
self.db.set('metadata-%s-%s' % (tenant_id, user_id), metadata)
+ user_ref = self._get_user(user_id)
+ tenants = set(user_ref.get('tenants', []))
+ if tenant_id not in tenants:
+ tenants.add(tenant_id)
+ user_ref['tenants'] = list(tenants)
+ self.update_user(user_id, user_ref)
else:
self.db.set('metadata-%s-%s' % (domain_id, user_id), metadata)
else:
diff --git a/keystone/identity/backends/ldap/core.py b/keystone/identity/backends/ldap/core.py
index db95e246..22a7fd0c 100644
--- a/keystone/identity/backends/ldap/core.py
+++ b/keystone/identity/backends/ldap/core.py
@@ -152,12 +152,6 @@ class Identity(identity.Driver):
def list_roles(self):
return self.role.get_all()
- # These should probably be part of the high-level API
- def add_user_to_project(self, tenant_id, user_id):
- self.get_project(tenant_id)
- self.get_user(user_id)
- return self.project.add_user(tenant_id, user_id)
-
def get_projects_for_user(self, user_id):
self.get_user(user_id)
return [p['id'] for p in self.project.get_user_projects(user_id)]
@@ -243,11 +237,6 @@ class Identity(identity.Driver):
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
return self.role.delete_user(role_id, user_id, tenant_id)
- def remove_user_from_project(self, tenant_id, user_id):
- self.get_user(user_id)
- self.get_project(tenant_id)
- return self.project.remove_user(tenant_id, user_id)
-
def update_role(self, role_id, role):
self.get_role(role_id)
self.role.update(role_id, role)
@@ -551,13 +540,17 @@ class ProjectApi(common_ldap.BaseLdap, ApiShimMixin):
def get_user_projects(self, user_id):
"""Returns list of tenants a user has access to
-
- Always includes default tenants.
"""
- user_dn = self.user_api._id_to_dn(user_id)
- query = '(%s=%s)' % (self.member_attribute, user_dn)
- memberships = self.get_all(query)
- return memberships
+ associations = self.role_api.list_project_roles_for_user(user_id)
+ project_ids = set()
+ for assoc in associations:
+ project_ids.add(assoc.project_id)
+ projects = []
+ for project_id in project_ids:
+ #slower to get them one at a time, but a huge list could blow out
+ #the connection. This is the safer way
+ projects.append(self.get(project_id))
+ return projects
def list_for_user_get_page(self, user, marker, limit):
return self._get_page(marker,
diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py
index 1d821287..bda2bf93 100644
--- a/keystone/identity/backends/sql.py
+++ b/keystone/identity/backends/sql.py
@@ -17,6 +17,7 @@
import functools
from keystone import clean
+from keystone import config
from keystone.common import sql
from keystone.common.sql import migration
from keystone.common import utils
@@ -82,7 +83,6 @@ class Domain(sql.ModelBase, sql.DictBase):
extra = sql.Column(sql.JsonBlob())
-# TODO(dolph): rename to Project
class Project(sql.ModelBase, sql.DictBase):
__tablename__ = 'project'
attributes = ['id', 'name', 'domain_id']
@@ -114,11 +114,13 @@ class BaseGrant(sql.DictBase):
class UserProjectGrant(sql.ModelBase, BaseGrant):
- # TODO(dolph): rename to user_project_metadata (needs a migration)
- __tablename__ = 'metadata'
- user_id = sql.Column(sql.String(64), primary_key=True)
- # TODO(dolph): rename to project_id (needs a migration)
- tenant_id = sql.Column(sql.String(64), primary_key=True)
+ __tablename__ = 'user_project_metadata'
+ user_id = sql.Column(sql.String(64),
+ sql.ForeignKey('user.id'),
+ primary_key=True)
+ project_id = sql.Column(sql.String(64),
+ sql.ForeignKey('project.id'),
+ primary_key=True)
data = sql.Column(sql.JsonBlob())
@@ -143,18 +145,6 @@ class GroupDomainGrant(sql.ModelBase, BaseGrant):
data = sql.Column(sql.JsonBlob())
-# TODO(dolph): ... do we need this table?
-class UserProjectMembership(sql.ModelBase, sql.DictBase):
- """Project membership join table."""
- __tablename__ = 'user_project_membership'
- user_id = sql.Column(sql.String(64),
- sql.ForeignKey('user.id'),
- primary_key=True)
- tenant_id = sql.Column(sql.String(64),
- sql.ForeignKey('project.id'),
- primary_key=True)
-
-
class UserGroupMembership(sql.ModelBase, sql.DictBase):
"""Group membership join table."""
__tablename__ = 'user_group_membership'
@@ -242,8 +232,8 @@ class Identity(sql.Base, identity.Driver):
session = self.get_session()
self.get_project(tenant_id)
query = session.query(User)
- query = query.join(UserProjectMembership)
- query = query.filter(UserProjectMembership.tenant_id == tenant_id)
+ query = query.join(UserProjectGrant)
+ query = query.filter(UserProjectGrant.project_id == tenant_id)
user_refs = query.all()
return [identity.filter_user(user_ref.to_dict())
for user_ref in user_refs]
@@ -255,7 +245,7 @@ class Identity(sql.Base, identity.Driver):
if user_id:
if tenant_id:
q = session.query(UserProjectGrant)
- q = q.filter_by(tenant_id=tenant_id)
+ q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(UserDomainGrant)
q = q.filter_by(domain_id=domain_id)
@@ -375,37 +365,6 @@ class Identity(sql.Base, identity.Driver):
self.update_metadata(user_id, project_id, metadata_ref,
domain_id, group_id)
- # These should probably be part of the high-level API
- def add_user_to_project(self, tenant_id, user_id):
- session = self.get_session()
- self.get_project(tenant_id)
- self.get_user(user_id)
- query = session.query(UserProjectMembership)
- query = query.filter_by(user_id=user_id)
- query = query.filter_by(tenant_id=tenant_id)
- rv = query.first()
- if rv:
- return
-
- with session.begin():
- session.add(UserProjectMembership(user_id=user_id,
- tenant_id=tenant_id))
- session.flush()
-
- def remove_user_from_project(self, tenant_id, user_id):
- session = self.get_session()
- self.get_project(tenant_id)
- self.get_user(user_id)
- query = session.query(UserProjectMembership)
- query = query.filter_by(user_id=user_id)
- query = query.filter_by(tenant_id=tenant_id)
- membership_ref = query.first()
- if membership_ref is None:
- raise exception.NotFound('User not found in tenant')
- with session.begin():
- session.delete(membership_ref)
- session.flush()
-
def list_projects(self):
session = self.get_session()
tenant_refs = session.query(Project).all()
@@ -414,10 +373,10 @@ class Identity(sql.Base, identity.Driver):
def get_projects_for_user(self, user_id):
session = self.get_session()
self.get_user(user_id)
- query = session.query(UserProjectMembership)
+ query = session.query(UserProjectGrant)
query = query.filter_by(user_id=user_id)
membership_refs = query.all()
- return [x.tenant_id for x in membership_refs]
+ return [x.project_id for x in membership_refs]
def get_roles_for_user_and_project(self, user_id, tenant_id):
self.get_user(user_id)
@@ -453,22 +412,24 @@ class Identity(sql.Base, identity.Driver):
def remove_role_from_user_and_project(self, user_id, tenant_id, role_id):
try:
metadata_ref = self.get_metadata(user_id, tenant_id)
- is_new = False
+ roles = set(metadata_ref.get('roles', []))
+ if role_id not in roles:
+ msg = _('Cannot remove role that has not been granted, %s' %
+ role_id)
+ raise exception.RoleNotFound(message=msg)
+ roles.remove(role_id)
+ metadata_ref['roles'] = list(roles)
+ if len(roles):
+ self.update_metadata(user_id, tenant_id, metadata_ref)
+ else:
+ session = self.get_session()
+ q = session.query(UserProjectGrant)
+ q = q.filter_by(project_id=tenant_id)
+ q.delete()
except exception.MetadataNotFound:
- metadata_ref = {}
- is_new = True
- roles = set(metadata_ref.get('roles', []))
- if role_id not in roles:
msg = 'Cannot remove role that has not been granted, %s' % role_id
raise exception.RoleNotFound(message=msg)
- roles.remove(role_id)
- metadata_ref['roles'] = list(roles)
- if is_new:
- self.create_metadata(user_id, tenant_id, metadata_ref)
- else:
- self.update_metadata(user_id, tenant_id, metadata_ref)
-
# CRUD
@handle_conflicts(type='project')
def create_project(self, tenant_id, tenant):
@@ -512,12 +473,12 @@ class Identity(sql.Base, identity.Driver):
raise exception.ProjectNotFound(project_id=tenant_id)
with session.begin():
- q = session.query(UserProjectMembership)
- q = q.filter_by(tenant_id=tenant_id)
+ q = session.query(UserProjectGrant)
+ q = q.filter_by(project_id=tenant_id)
q.delete(False)
q = session.query(UserProjectGrant)
- q = q.filter_by(tenant_id=tenant_id)
+ q = q.filter_by(project_id=tenant_id)
q.delete(False)
q = session.query(GroupProjectGrant)
@@ -539,7 +500,7 @@ class Identity(sql.Base, identity.Driver):
if user_id:
if tenant_id:
session.add(UserProjectGrant(user_id=user_id,
- tenant_id=tenant_id,
+ project_id=tenant_id,
data=metadata))
elif domain_id:
session.add(UserDomainGrant(user_id=user_id,
@@ -566,7 +527,7 @@ class Identity(sql.Base, identity.Driver):
if tenant_id:
q = session.query(UserProjectGrant)
q = q.filter_by(user_id=user_id)
- q = q.filter_by(tenant_id=tenant_id)
+ q = q.filter_by(project_id=tenant_id)
elif domain_id:
q = session.query(UserDomainGrant)
q = q.filter_by(user_id=user_id)
@@ -651,7 +612,7 @@ class Identity(sql.Base, identity.Driver):
metadata_refs = session\
.query(UserProjectGrant)\
.filter_by(user_id=user_id)
- project_ids = set([x.tenant_id for x in metadata_refs
+ project_ids = set([x.project_id for x in metadata_refs
if x.data.get('roles')])
if user.get('project_id'):
project_ids.add(user['project_id'])
@@ -797,9 +758,6 @@ class Identity(sql.Base, identity.Driver):
raise exception.UserNotFound(user_id=user_id)
with session.begin():
- q = session.query(UserProjectMembership)
- q = q.filter_by(user_id=user_id)
- q.delete(False)
q = session.query(UserProjectGrant)
q = q.filter_by(user_id=user_id)
@@ -999,7 +957,7 @@ class Identity(sql.Base, identity.Driver):
metadata = metadata_ref.to_dict()
try:
self.remove_role_from_user_and_project(
- metadata['user_id'], metadata['tenant_id'], role_id)
+ metadata['user_id'], metadata['project_id'], role_id)
except exception.RoleNotFound:
pass
diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py
index 8060e28f..31125267 100644
--- a/keystone/identity/controllers.py
+++ b/keystone/identity/controllers.py
@@ -304,9 +304,6 @@ class Role(controller.V2Controller):
raise exception.NotImplemented(message='User roles not supported: '
'tenant_id required')
- # This still has the weird legacy semantics that adding a role to
- # a user also adds them to a tenant
- self.identity_api.add_user_to_project(context, tenant_id, user_id)
self.identity_api.add_role_to_user_and_project(
context, user_id, tenant_id, role_id)
self.token_api.revoke_tokens(context, user_id, tenant_id)
@@ -332,9 +329,6 @@ class Role(controller.V2Controller):
context, user_id, tenant_id, role_id)
roles = self.identity_api.get_roles_for_user_and_project(
context, user_id, tenant_id)
- if not roles:
- self.identity_api.remove_user_from_project(
- context, tenant_id, user_id)
self.token_api.revoke_tokens(context, user_id, tenant_id)
# COMPAT(diablo): CRUD extension
@@ -375,7 +369,6 @@ class Role(controller.V2Controller):
# TODO(termie): for now we're ignoring the actual role
tenant_id = role.get('tenantId')
role_id = role.get('roleId')
- self.identity_api.add_user_to_project(context, tenant_id, user_id)
self.identity_api.add_role_to_user_and_project(
context, user_id, tenant_id, role_id)
self.token_api.revoke_tokens(context, user_id, tenant_id)
@@ -404,9 +397,6 @@ class Role(controller.V2Controller):
context, user_id, tenant_id, role_id)
roles = self.identity_api.get_roles_for_user_and_project(
context, user_id, tenant_id)
- if not roles:
- self.identity_api.remove_user_from_project(
- context, tenant_id, user_id)
self.token_api.revoke_tokens(context, user_id, tenant_id)
diff --git a/keystone/identity/core.py b/keystone/identity/core.py
index 7810d3f4..7d8c991f 100644
--- a/keystone/identity/core.py
+++ b/keystone/identity/core.py
@@ -102,22 +102,28 @@ class Driver(object):
raise exception.NotImplemented()
def add_user_to_project(self, tenant_id, user_id):
- """Add user to a tenant without an explicit role relationship.
+ """Add user to a tenant by creating a default role relationship.
- :raises: keystone.exception.ProjectNotFound,
- keystone.exception.UserNotFound
+ :raises: keystone.exception.ProjectNotFound,
+ keystone.exception.UserNotFound
- """
- raise exception.NotImplemented()
+ """
+ self.add_role_to_user_and_project(user_id,
+ tenant_id,
+ config.CONF.member_role_id)
def remove_user_from_project(self, tenant_id, user_id):
- """Remove user from a tenant without an explicit role relationship.
+ """Remove user from a tenant
:raises: keystone.exception.ProjectNotFound,
keystone.exception.UserNotFound
"""
- raise exception.NotImplemented()
+ roles = self.get_roles_for_user_and_project(user_id, tenant_id)
+ if not roles:
+ raise exception.NotFound(tenant_id)
+ for role_id in roles:
+ self.remove_role_from_user_and_project(user_id, tenant_id, role_id)
def get_project_users(self, tenant_id):
"""Lists all users with a relationship to the specified project.