Rationalize list_user_projects and get_projects_for_user

These two functions have slightly different implementations and yet neither
takes into account group or inherited roles.  Rationalize these into a single
list_projects_for_user() that gets the correct list of project refs, allowing
for group and inherited roles.  The public API name, as referenced in the
policy file, remains as 'list_user_projects' to avoid a policy file change
at this late stage.

The most common use cases of the above is via the v2 API, when we need
to ensure only projects from the default domain are included.  Given that
this means we must inspect each project ref anyway, having a call that
returns a list of IDs will not be any more efficient than returning the
project refs and letting the caller extract the IDs.

Fixes bug #1201487

Change-Id: I614140639c71dae4dfe501d27eda65e41a78694d
This commit is contained in:
Henry Nash 2013-09-08 21:07:07 +01:00
parent 54b8ec55e6
commit fb6b5eba63
13 changed files with 284 additions and 103 deletions

View File

@ -101,9 +101,12 @@ class Assignment(kvs.Base, assignment.Driver):
role_ids = self.db.get('role_list', [])
return [self.get_role(x) for x in role_ids]
def get_projects_for_user(self, user_id):
def list_projects_for_user(self, user_id, group_ids):
# NOTE(henry-nash): The kvs backend is being deprecated, so no
# support is provided for projects that the user has a role on solely
# by virtue of group membership.
user_ref = self._get_user(user_id)
return user_ref.get('tenants', [])
return [self.get_project(x) for x in user_ref.get('tenants', [])]
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
self.identity_api.get_user(user_id)

View File

@ -115,12 +115,18 @@ class Assignment(assignment.Driver):
def list_roles(self):
return self.role.get_all()
def get_projects_for_user(self, user_id):
def list_projects_for_user(self, user_id, group_ids):
# NOTE(henry-nash): The LDAP backend is being deprecated, so no
# support is provided for projects that the user has a role on solely
# by virtue of group membership.
self.identity_api.get_user(user_id)
user_dn = self.user._id_to_dn(user_id)
associations = (self.role.list_project_roles_for_user
(user_dn, self.project.tree_dn))
return [p['id'] for p in
# Since the LDAP backend doesn't store the domain_id in the LDAP
# records (and only supports the default domain), we fill in the
# domain_id before we return the list.
return [self._set_default_domain(x) for x in
self.project.get_user_projects(user_dn, associations)]
def get_project_users(self, tenant_id):

View File

@ -19,9 +19,13 @@ from keystone import clean
from keystone.common import dependency
from keystone.common import sql
from keystone.common.sql import migration
from keystone import config
from keystone import exception
CONF = config.CONF
@dependency.requires('identity_api')
class Assignment(sql.Base, assignment.Driver):
@ -219,21 +223,76 @@ class Assignment(sql.Base, assignment.Driver):
project_refs = query.all()
return [project_ref.to_dict() for project_ref in project_refs]
def get_projects_for_user(self, user_id):
def list_projects_for_user(self, user_id, group_ids):
# NOTE(henry-nash): This method is written as a series of code blocks,
# rather than broken down into too many sub-functions, to prepare for
# SQL optimization when we rationalize the grant tables in the
# future.
# FIXME(henry-nash) The following should take into account
# both group and inherited roles. In fact, I don't see why this
# call can't be handled at the controller level like we do
# with 'get_roles_for_user_and_project()'. Further, this
# call seems essentially the same as 'list_user_projects()'
# later in this driver. Both should be removed.
def _list_domains_with_inherited_grants(query):
domain_ids = set()
domain_grants = query.all()
for domain_grant in domain_grants:
for grant in domain_grant.data.get('roles', []):
if 'inherited_to' in grant:
domain_ids.add(domain_grant.domain_id)
return domain_ids
self.identity_api.get_user(user_id)
# NOTE(henry-nash): The metadata management code doesn't always clean
# up table entries when the last role is deleted - so when checking
# grant entries, only include this project if there are actually roles
# present.
# First get a list of the projects for which the user has a direct
# role assigned
session = self.get_session()
query = session.query(UserProjectGrant)
query = query.filter_by(user_id=user_id)
membership_refs = query.all()
return [x.project_id for x in membership_refs]
project_grants_for_user = query.all()
project_ids = set(x.project_id for x in project_grants_for_user
if x.data.get('roles'))
# Now find any projects with group roles and add them in
for group_id in group_ids:
query = session.query(GroupProjectGrant)
query = query.filter_by(group_id=group_id)
project_grants_for_group = query.all()
for project_grant in project_grants_for_group:
if project_grant.data.get('roles'):
project_ids.add(project_grant.project_id)
if not CONF.os_inherit.enabled:
return [self.get_project(x) for x in project_ids]
# Inherited roles are enabled, so check to see if this user has any
# such roles (direct or group) on any domain, in which case we must
# add in all the projects in that domain.
domain_ids = set()
# First check for user roles on any domains
query = session.query(UserDomainGrant)
query = query.filter_by(user_id=user_id)
domain_ids.update(_list_domains_with_inherited_grants(query))
# Now for group roles on any domains
for group_id in group_ids:
query = session.query(GroupDomainGrant)
query = query.filter_by(group_id=group_id)
domain_ids.update(_list_domains_with_inherited_grants(query))
# For each domain on which the user has an inherited role, get the
# list of projects in that domain and add them in to the
# project id list
for domain_id in domain_ids:
query = session.query(Project)
query = query.filter_by(domain_id=domain_id)
project_refs = query.all()
for project_ref in project_refs:
project_ids.add(project_ref.id)
return [self.get_project(x) for x in project_ids]
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
self.identity_api.get_user(user_id)
@ -508,31 +567,6 @@ class Assignment(sql.Base, assignment.Driver):
session.delete(ref)
session.flush()
def list_user_projects(self, user_id):
# FIXME(henry-nash) The following should take into account
# both group and inherited roles. In fact, I don't see why this
# call can't be handled at the controller level like we do
# with 'get_roles_for_user_and_project()'. Further, this
# call seems essentially the same as 'get_projects_for_user()'
# earlier in this driver. Both should be removed.
session = self.get_session()
user = self.identity_api.get_user(user_id)
metadata_refs = session\
.query(UserProjectGrant)\
.filter_by(user_id=user_id)
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'])
# FIXME(dolph): this should be removed with proper migrations
if user.get('tenant_id'):
project_ids.add(user['tenant_id'])
return [self.get_project(x) for x in project_ids]
# role crud
@sql.handle_conflicts(type='role')

View File

@ -243,6 +243,18 @@ class Manager(manager.Manager):
for role_id in roles:
self.remove_role_from_user_and_project(user_id, tenant_id, role_id)
def list_projects_for_user(self, user_id):
# NOTE(henry-nash): In order to get a complete list of user projects,
# the driver will need to look at group assignments. To avoid cross
# calling between the assignment and identity driver we get the group
# list here and pass it in. The rest of the detailed logic of listing
# projects for a user is pushed down into the driver to enable
# optimization with the various backend technologies (SQL, LDAP etc.).
group_ids = [x['id'] for
x in self.identity_api.list_groups_for_user(user_id)]
return self.driver.list_projects_for_user(user_id, group_ids)
@cache.on_arguments(should_cache_fn=SHOULD_CACHE,
expiration_time=CONF.assignment.cache_time)
def get_domain(self, domain_id):
@ -357,15 +369,6 @@ class Driver(object):
"""
raise exception.NotImplemented()
def get_projects_for_user(self, user_id):
"""Get the tenants associated with a given user.
:returns: a list of tenant_id's.
:raises: keystone.exception.UserNotFound
"""
raise exception.NotImplemented()
def add_role_to_user_and_project(self, user_id, tenant_id, role_id):
"""Add a role to a user within given tenant.
@ -524,9 +527,14 @@ class Driver(object):
"""
raise exception.NotImplemented()
def list_user_projects(self, user_id):
def list_projects_for_user(self, user_id, group_ids):
"""List all projects associated with a given user.
:param user_id: the user in question
:param group_ids: the groups this user is a member of. This list is
built in the Manager, so that the driver itself
does not have to call across to identity.
:returns: a list of project_refs or an empty list.
"""

View File

@ -336,7 +336,7 @@ class OAuthControllerV3(controller.V3Controller):
# verify the user has the project too
req_project_id = req_token['requested_project_id']
user_projects = self.assignment_api.list_user_projects(user_id)
user_projects = self.assignment_api.list_projects_for_user(user_id)
found = False
for user_project in user_projects:
if user_project['id'] == req_project_id:

View File

@ -99,8 +99,8 @@ class PamIdentity(identity.Driver):
def remove_user_from_project(self, tenant_id, user_id):
pass
def get_projects_for_user(self, user_id):
return [user_id]
def list_projects_for_user(self, user_id):
return [{'id': user_id, 'name': user_id}]
def get_roles_for_user_and_project(self, user_id, tenant_id):
raise NotImplementedError()

View File

@ -64,11 +64,10 @@ class Tenant(controller.V2Controller):
raise exception.Unauthorized(e)
user_ref = token_ref['user']
tenant_ids = self.identity_api.get_projects_for_user(user_ref['id'])
tenant_refs = []
for tenant_id in tenant_ids:
ref = self.identity_api.get_project(tenant_id)
tenant_refs.append(self._filter_domain_id(ref))
tenant_refs = (
self.assignment_api.list_projects_for_user(user_ref['id']))
tenant_refs = [self._filter_domain_id(ref) for ref in tenant_refs
if ref['domain_id'] == DEFAULT_DOMAIN_ID]
params = {
'limit': context['query_string'].get('limit'),
'marker': context['query_string'].get('marker'),
@ -350,14 +349,18 @@ class Role(controller.V2Controller):
self.assert_admin(context)
# Ensure user exists by getting it first.
self.identity_api.get_user(user_id)
tenant_ids = self.identity_api.get_projects_for_user(user_id)
tenants = self.assignment_api.list_projects_for_user(user_id)
o = []
for tenant_id in tenant_ids:
for tenant in tenants:
# As a v2 call, we should limit the response to those projects in
# the default domain.
if tenant['domain_id'] != DEFAULT_DOMAIN_ID:
continue
role_ids = self.identity_api.get_roles_for_user_and_project(
user_id, tenant_id)
user_id, tenant['id'])
for role_id in role_ids:
ref = {'roleId': role_id,
'tenantId': tenant_id,
'tenantId': tenant['id'],
'userId': user_id}
ref['id'] = urllib.urlencode(ref)
o.append(ref)
@ -575,7 +578,7 @@ class ProjectV3(controller.V3Controller):
@controller.filterprotected('enabled', 'name')
def list_user_projects(self, context, filters, user_id):
refs = self.identity_api.list_user_projects(user_id)
refs = self.identity_api.list_projects_for_user(user_id)
return ProjectV3.wrap_collection(context, refs, filters)
@controller.protected()

View File

@ -426,9 +426,6 @@ class Manager(manager.Manager):
def list_roles(self):
return self.assignment_api.list_roles()
def get_projects_for_user(self, user_id):
return self.assignment_api.get_projects_for_user(user_id)
def get_project_users(self, tenant_id):
return self.assignment_api.get_project_users(tenant_id)
@ -508,8 +505,8 @@ class Manager(manager.Manager):
def list_domains(self):
return self.assignment_api.list_domains()
def list_user_projects(self, user_id):
return self.assignment_api.list_user_projects(user_id)
def list_projects_for_user(self, user_id):
return self.assignment_api.list_projects_for_user(user_id)
def add_user_to_project(self, tenant_id, user_id):
return self.assignment_api.add_user_to_project(tenant_id, user_id)

View File

@ -1451,8 +1451,9 @@ class IdentityTests(object):
def test_add_user_to_project(self):
self.identity_api.add_user_to_project(self.tenant_baz['id'],
self.user_foo['id'])
tenants = self.identity_api.get_projects_for_user(self.user_foo['id'])
self.assertIn(self.tenant_baz['id'], tenants)
tenants = self.assignment_api.list_projects_for_user(
self.user_foo['id'])
self.assertIn(self.tenant_baz, tenants)
def test_add_user_to_project_missing_default_role(self):
self.assignment_api.delete_role(CONF.member_role_id)
@ -1461,8 +1462,9 @@ class IdentityTests(object):
CONF.member_role_id)
self.identity_api.add_user_to_project(self.tenant_baz['id'],
self.user_foo['id'])
tenants = self.identity_api.get_projects_for_user(self.user_foo['id'])
self.assertIn(self.tenant_baz['id'], tenants)
tenants = (
self.assignment_api.list_projects_for_user(self.user_foo['id']))
self.assertIn(self.tenant_baz, tenants)
default_role = self.assignment_api.get_role(CONF.member_role_id)
self.assertIsNotNone(default_role)
@ -1482,8 +1484,9 @@ class IdentityTests(object):
self.user_foo['id'])
self.identity_api.remove_user_from_project(self.tenant_baz['id'],
self.user_foo['id'])
tenants = self.identity_api.get_projects_for_user(self.user_foo['id'])
self.assertNotIn(self.tenant_baz['id'], tenants)
tenants = self.assignment_api.list_projects_for_user(
self.user_foo['id'])
self.assertNotIn(self.tenant_baz, tenants)
def test_remove_user_from_project_404(self):
self.assertRaises(exception.ProjectNotFound,
@ -1501,9 +1504,9 @@ class IdentityTests(object):
self.tenant_baz['id'],
self.user_foo['id'])
def test_get_projects_for_user_404(self):
def test_list_user_project_ids_404(self):
self.assertRaises(exception.UserNotFound,
self.identity_api.get_projects_for_user,
self.assignment_api.list_projects_for_user,
uuid.uuid4().hex)
def test_update_project_404(self):
@ -1535,7 +1538,7 @@ class IdentityTests(object):
user['id'])
self.identity_api.delete_user(user['id'])
self.assertRaises(exception.UserNotFound,
self.identity_api.get_projects_for_user,
self.assignment_api.list_projects_for_user,
user['id'])
def test_delete_user_with_project_roles(self):
@ -1550,7 +1553,7 @@ class IdentityTests(object):
self.role_member['id'])
self.identity_api.delete_user(user['id'])
self.assertRaises(exception.UserNotFound,
self.identity_api.get_projects_for_user,
self.assignment_api.list_projects_for_user,
user['id'])
def test_delete_user_404(self):
@ -2263,14 +2266,14 @@ class IdentityTests(object):
self.identity_api.get_user,
user['id'])
def test_list_user_projects(self):
def test_list_projects_for_user(self):
domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
self.identity_api.create_domain(domain['id'], domain)
user1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'password': uuid.uuid4().hex, 'domain_id': domain['id'],
'enabled': True}
self.identity_api.create_user(user1['id'], user1)
user_projects = self.identity_api.list_user_projects(user1['id'])
user_projects = self.assignment_api.list_projects_for_user(user1['id'])
self.assertEquals(len(user_projects), 0)
self.identity_api.create_grant(user_id=user1['id'],
project_id=self.tenant_bar['id'],
@ -2278,9 +2281,47 @@ class IdentityTests(object):
self.identity_api.create_grant(user_id=user1['id'],
project_id=self.tenant_baz['id'],
role_id=self.role_member['id'])
user_projects = self.identity_api.list_user_projects(user1['id'])
user_projects = self.assignment_api.list_projects_for_user(user1['id'])
self.assertEquals(len(user_projects), 2)
def test_list_projects_for_user_with_grants(self):
# Create two groups each with a role on a different project, and
# make user1 a member of both groups. Both these new projects
# should now be included, along with any direct user grants.
domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
self.identity_api.create_domain(domain['id'], domain)
user1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'password': uuid.uuid4().hex, 'domain_id': domain['id'],
'enabled': True}
self.identity_api.create_user(user1['id'], user1)
group1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain['id']}
self.identity_api.create_group(group1['id'], group1)
group2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain['id']}
self.identity_api.create_group(group2['id'], group2)
project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain['id']}
self.assignment_api.create_project(project1['id'], project1)
project2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain['id']}
self.assignment_api.create_project(project2['id'], project2)
self.identity_api.add_user_to_group(user1['id'], group1['id'])
self.identity_api.add_user_to_group(user1['id'], group2['id'])
# Create 3 grants, one user grant, the other two as group grants
self.identity_api.create_grant(user_id=user1['id'],
project_id=self.tenant_bar['id'],
role_id=self.role_member['id'])
self.identity_api.create_grant(group_id=group1['id'],
project_id=project1['id'],
role_id=self.role_admin['id'])
self.identity_api.create_grant(group_id=group2['id'],
project_id=project2['id'],
role_id=self.role_admin['id'])
user_projects = self.assignment_api.list_projects_for_user(user1['id'])
self.assertEquals(len(user_projects), 3)
def test_cache_layer_domain_crud(self):
domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'enabled': True}
@ -3171,3 +3212,107 @@ class InheritanceTests(object):
self.assertIn(role_list[0]['id'], combined_role_list)
self.assertIn(role_list[2]['id'], combined_role_list)
self.assertIn(role_list[3]['id'], combined_role_list)
def test_list_projects_for_user_with_inherited_grants(self):
"""Test inherited group roles.
Test Plan:
- Enable OS-INHERIT extension
- Create a domain, with two projects and a user
- Assign an inherited user role on the domain, as well as a direct
user role to a separate project in a different domain
- Get a list of projects for user, should return all three projects
"""
self.opt_in_group('os_inherit', enabled=True)
domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
self.identity_api.create_domain(domain['id'], domain)
user1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'password': uuid.uuid4().hex, 'domain_id': domain['id'],
'enabled': True}
self.identity_api.create_user(user1['id'], user1)
project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain['id']}
self.assignment_api.create_project(project1['id'], project1)
project2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain['id']}
self.assignment_api.create_project(project2['id'], project2)
# Create 2 grants, one on a project and one inherited grant
# on the domain
self.identity_api.create_grant(user_id=user1['id'],
project_id=self.tenant_bar['id'],
role_id=self.role_member['id'])
self.identity_api.create_grant(user_id=user1['id'],
domain_id=domain['id'],
role_id=self.role_admin['id'],
inherited_to_projects=True)
# Should get back all three projects, one by virtue of the direct
# grant, plus both projects in the domain
user_projects = self.assignment_api.list_projects_for_user(user1['id'])
self.assertEquals(len(user_projects), 3)
def test_list_projects_for_user_with_inherited_group_grants(self):
"""Test inherited group roles.
Test Plan:
- Enable OS-INHERIT extension
- Create two domains, each with two projects
- Create a user and group
- Make the user a member of the group
- Assign a user role two projects, an inherited
group role to one domain and an inherited regular role on
the other domain
- Get a list of projects for user, should return both pairs of projects
from the domain, plus the one separate project
"""
self.opt_in_group('os_inherit', enabled=True)
domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
self.identity_api.create_domain(domain['id'], domain)
domain2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
self.identity_api.create_domain(domain2['id'], domain2)
project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain['id']}
self.assignment_api.create_project(project1['id'], project1)
project2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain['id']}
self.assignment_api.create_project(project2['id'], project2)
project3 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain2['id']}
self.assignment_api.create_project(project3['id'], project3)
project4 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain2['id']}
self.assignment_api.create_project(project4['id'], project4)
user1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'password': uuid.uuid4().hex, 'domain_id': domain['id'],
'enabled': True}
self.identity_api.create_user(user1['id'], user1)
group1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
'domain_id': domain['id']}
self.identity_api.create_group(group1['id'], group1)
self.identity_api.add_user_to_group(user1['id'], group1['id'])
# Create 4 grants:
# - one user grant on a project in domain2
# - one user grant on a project in the default domain
# - one inherited user grant on domain
# - one inherited group grant on domain2
self.identity_api.create_grant(user_id=user1['id'],
project_id=project3['id'],
role_id=self.role_member['id'])
self.identity_api.create_grant(user_id=user1['id'],
project_id=self.tenant_bar['id'],
role_id=self.role_member['id'])
self.identity_api.create_grant(user_id=user1['id'],
domain_id=domain['id'],
role_id=self.role_admin['id'],
inherited_to_projects=True)
self.identity_api.create_grant(group_id=group1['id'],
domain_id=domain2['id'],
role_id=self.role_admin['id'],
inherited_to_projects=True)
# Should get back all five projects, but without a duplicate for
# project3 (since it has both a direct user role and an inherited role)
user_projects = self.assignment_api.list_projects_for_user(user1['id'])
self.assertEquals(len(user_projects), 5)

View File

@ -30,9 +30,8 @@ class KvsIdentity(tests.TestCase, test_backend.IdentityTests):
self.load_backends()
self.load_fixtures(default_fixtures)
def test_list_user_projects(self):
# NOTE(chungg): not implemented
self.skipTest('Blocked by bug 1119770')
def test_list_projects_for_user_with_grants(self):
self.skipTest('kvs backend is now deprecated')
def test_create_duplicate_group_name_in_different_domains(self):
self.skipTest('Blocked by bug 1119770')

View File

@ -169,9 +169,12 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
def test_delete_group_with_user_project_domain_links(self):
self.skipTest('N/A: LDAP does not support multiple domains')
def test_list_user_projects(self):
def test_list_projects_for_user(self):
self.skipTest('Blocked by bug 1101287')
def test_list_projects_for_user_with_grants(self):
self.skipTest('Blocked by bug 1221805')
def test_create_duplicate_user_name_in_different_domains(self):
self.skipTest('Blocked by bug 1101276')

View File

@ -161,7 +161,7 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
user['id'])
self.identity_api.delete_user(user['id'])
self.assertRaises(exception.UserNotFound,
self.identity_api.get_projects_for_user,
self.assignment_api.list_projects_for_user,
user['id'])
def test_create_null_user_name(self):
@ -217,7 +217,7 @@ class SqlIdentity(SqlTests, test_backend.IdentityTests):
self.identity_api.add_user_to_project(self.tenant_bar['id'],
user['id'])
self.assignment_api.delete_project(self.tenant_bar['id'])
tenants = self.identity_api.get_projects_for_user(user['id'])
tenants = self.assignment_api.list_projects_for_user(user['id'])
self.assertEquals(tenants, [])
def test_metadata_removed_on_delete_user(self):

View File

@ -352,23 +352,6 @@ class Auth(controller.V2Controller):
raise exception.Unauthorized(e)
return domain_id
def _get_project_ref(self, user_id, tenant_id):
"""Returns the tenant_ref for the user's tenant."""
tenant_ref = None
if tenant_id:
tenants = self.identity_api.get_projects_for_user(user_id)
if tenant_id not in tenants:
msg = 'User %s is unauthorized for tenant %s' % (
user_id, tenant_id)
LOG.warning(msg)
raise exception.Unauthorized(msg)
try:
tenant_ref = self.identity_api.get_project(tenant_id)
except exception.ProjectNotFound as e:
exception.Unauthorized(e)
return tenant_ref
def _get_project_roles_and_ref(self, user_id, tenant_id):
"""Returns the project roles for this user, and the project ref."""