This is the first part of the more comprehensive split of assignments, which rationalizes both the backend and controllers. In order to make this change easier for reviewers, it is divided into a number of smaller patches. Follow-on patches will: - Fix incorrect doc strings for grant driver methods - Update unit tests to call the new role manager - Update the assignment controller to call the role manager - Refactor assignment manager and driver methods to logically separate project/domains from the actual assignments - Split projects and domains into their own backend - Split the controllers so they call the correct manager - Update the tests to call the new correct manager Partially implements: bp pluggable-assignments Change-Id: I41fc23a049c26e514222a966c1847e183448be00changes/39/144239/18
parent
ab063791d3
commit
3408515e8c
@ -0,0 +1,124 @@
|
||||
# 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.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from keystone import assignment
|
||||
from keystone.common import ldap as common_ldap
|
||||
from keystone.common import models
|
||||
from keystone import config
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
from keystone.identity.backends import ldap as ldap_identity
|
||||
from keystone.openstack.common import log
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Role(assignment.RoleDriver):
|
||||
|
||||
def __init__(self):
|
||||
super(Role, self).__init__()
|
||||
self.LDAP_URL = CONF.ldap.url
|
||||
self.LDAP_USER = CONF.ldap.user
|
||||
self.LDAP_PASSWORD = CONF.ldap.password
|
||||
self.suffix = CONF.ldap.suffix
|
||||
|
||||
# This is the only deep dependency from resource back
|
||||
# to identity. The assumption is that if you are using
|
||||
# LDAP for resource, you are using it for identity as well.
|
||||
self.user = ldap_identity.UserApi(CONF)
|
||||
self.role = RoleApi(CONF, self.user)
|
||||
|
||||
def get_role(self, role_id):
|
||||
return self.role.get(role_id)
|
||||
|
||||
def list_roles(self, hints):
|
||||
return self.role.get_all()
|
||||
|
||||
def list_roles_from_ids(self, ids):
|
||||
return [self.get_role(id) for id in ids]
|
||||
|
||||
def create_role(self, role_id, role):
|
||||
self.role.check_allow_create()
|
||||
try:
|
||||
self.get_role(role_id)
|
||||
except exception.NotFound:
|
||||
pass
|
||||
else:
|
||||
msg = _('Duplicate ID, %s.') % role_id
|
||||
raise exception.Conflict(type='role', details=msg)
|
||||
|
||||
try:
|
||||
self.role.get_by_name(role['name'])
|
||||
except exception.NotFound:
|
||||
pass
|
||||
else:
|
||||
msg = _('Duplicate name, %s.') % role['name']
|
||||
raise exception.Conflict(type='role', details=msg)
|
||||
|
||||
return self.role.create(role)
|
||||
|
||||
def delete_role(self, role_id):
|
||||
self.role.check_allow_delete()
|
||||
return self.role.delete(role_id)
|
||||
|
||||
def update_role(self, role_id, role):
|
||||
self.role.check_allow_update()
|
||||
self.get_role(role_id)
|
||||
return self.role.update(role_id, role)
|
||||
|
||||
|
||||
# NOTE(heny-nash): A mixin class to enable the sharing of the LDAP structure
|
||||
# between here and the assignment LDAP.
|
||||
class RoleLdapStructureMixin(object):
|
||||
DEFAULT_OU = 'ou=Roles'
|
||||
DEFAULT_STRUCTURAL_CLASSES = []
|
||||
DEFAULT_OBJECTCLASS = 'organizationalRole'
|
||||
DEFAULT_MEMBER_ATTRIBUTE = 'roleOccupant'
|
||||
NotFound = exception.RoleNotFound
|
||||
options_name = 'role'
|
||||
attribute_options_names = {'name': 'name'}
|
||||
immutable_attrs = ['id']
|
||||
model = models.Role
|
||||
|
||||
|
||||
# TODO(termie): turn this into a data object and move logic to driver
|
||||
class RoleApi(RoleLdapStructureMixin, common_ldap.BaseLdap):
|
||||
|
||||
def __init__(self, conf, user_api):
|
||||
super(RoleApi, self).__init__(conf)
|
||||
self._user_api = user_api
|
||||
|
||||
def get(self, role_id, role_filter=None):
|
||||
model = super(RoleApi, self).get(role_id, role_filter)
|
||||
return model
|
||||
|
||||
def create(self, values):
|
||||
return super(RoleApi, self).create(values)
|
||||
|
||||
def update(self, role_id, role):
|
||||
new_name = role.get('name')
|
||||
if new_name is not None:
|
||||
try:
|
||||
old_role = self.get_by_name(new_name)
|
||||
if old_role['id'] != role_id:
|
||||
raise exception.Conflict(
|
||||
_('Cannot duplicate name %s') % old_role)
|
||||
except exception.NotFound:
|
||||
pass
|
||||
return super(RoleApi, self).update(role_id, role)
|
||||
|
||||
def delete(self, role_id):
|
||||
super(RoleApi, self).delete(role_id)
|
@ -0,0 +1,80 @@
|
||||
# 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.
|
||||
|
||||
from keystone import assignment
|
||||
from keystone.common import sql
|
||||
from keystone import exception
|
||||
|
||||
|
||||
class Role(assignment.RoleDriver):
|
||||
|
||||
@sql.handle_conflicts(conflict_type='role')
|
||||
def create_role(self, role_id, role):
|
||||
with sql.transaction() as session:
|
||||
ref = RoleTable.from_dict(role)
|
||||
session.add(ref)
|
||||
return ref.to_dict()
|
||||
|
||||
@sql.truncated
|
||||
def list_roles(self, hints):
|
||||
with sql.transaction() as session:
|
||||
query = session.query(RoleTable)
|
||||
refs = sql.filter_limit_query(RoleTable, query, hints)
|
||||
return [ref.to_dict() for ref in refs]
|
||||
|
||||
def list_roles_from_ids(self, ids):
|
||||
if not ids:
|
||||
return []
|
||||
else:
|
||||
with sql.transaction() as session:
|
||||
query = session.query(RoleTable)
|
||||
query = query.filter(RoleTable.id.in_(ids))
|
||||
role_refs = query.all()
|
||||
return [role_ref.to_dict() for role_ref in role_refs]
|
||||
|
||||
def _get_role(self, session, role_id):
|
||||
ref = session.query(RoleTable).get(role_id)
|
||||
if ref is None:
|
||||
raise exception.RoleNotFound(role_id=role_id)
|
||||
return ref
|
||||
|
||||
def get_role(self, role_id):
|
||||
with sql.transaction() as session:
|
||||
return self._get_role(session, role_id).to_dict()
|
||||
|
||||
@sql.handle_conflicts(conflict_type='role')
|
||||
def update_role(self, role_id, role):
|
||||
with sql.transaction() as session:
|
||||
ref = self._get_role(session, role_id)
|
||||
old_dict = ref.to_dict()
|
||||
for k in role:
|
||||
old_dict[k] = role[k]
|
||||
new_role = RoleTable.from_dict(old_dict)
|
||||
for attr in RoleTable.attributes:
|
||||
if attr != 'id':
|
||||
setattr(ref, attr, getattr(new_role, attr))
|
||||
ref.extra = new_role.extra
|
||||
return ref.to_dict()
|
||||
|
||||
def delete_role(self, role_id):
|
||||
with sql.transaction() as session:
|
||||
ref = self._get_role(session, role_id)
|
||||
session.delete(ref)
|
||||
|
||||
|
||||
class RoleTable(sql.ModelBase, sql.DictBase):
|
||||
__tablename__ = 'role'
|
||||
attributes = ['id', 'name']
|
||||
id = sql.Column(sql.String(64), primary_key=True)
|
||||
name = sql.Column(sql.String(255), unique=True, nullable=False)
|
||||
extra = sql.Column(sql.JsonBlob())
|
||||
__table_args__ = (sql.UniqueConstraint('name'), {})
|