diff options
Diffstat (limited to 'keystone-moon/keystone/assignment/role_backends')
-rw-r--r-- | keystone-moon/keystone/assignment/role_backends/sql.py | 144 |
1 files changed, 133 insertions, 11 deletions
diff --git a/keystone-moon/keystone/assignment/role_backends/sql.py b/keystone-moon/keystone/assignment/role_backends/sql.py index 3c707aa8..1045f23a 100644 --- a/keystone-moon/keystone/assignment/role_backends/sql.py +++ b/keystone-moon/keystone/assignment/role_backends/sql.py @@ -9,24 +9,44 @@ # 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 oslo_db import exception as db_exception from keystone import assignment +from keystone.common import driver_hints from keystone.common import sql from keystone import exception +# NOTE(henry-nash): From the manager and above perspective, the domain_id +# attribute of a role is nullable. However, to ensure uniqueness in +# multi-process configurations, it is better to still use a sql uniqueness +# constraint. Since the support for a nullable component of a uniqueness +# constraint across different sql databases is mixed, we instead store a +# special value to represent null, as defined in NULL_DOMAIN_ID below. +NULL_DOMAIN_ID = '<<null>>' -class Role(assignment.RoleDriverV8): + +class Role(assignment.RoleDriverV9): @sql.handle_conflicts(conflict_type='role') def create_role(self, role_id, role): - with sql.transaction() as session: + with sql.session_for_write() as session: ref = RoleTable.from_dict(role) session.add(ref) return ref.to_dict() - @sql.truncated + @driver_hints.truncated def list_roles(self, hints): - with sql.transaction() as session: + # If there is a filter on domain_id and the value is None, then to + # ensure that the sql filtering works correctly, we need to patch + # the value to be NULL_DOMAIN_ID. This is safe to do here since we + # know we are able to satisfy any filter of this type in the call to + # filter_limit_query() below, which will remove the filter from the + # hints (hence ensuring our substitution is not exposed to the caller). + for f in hints.filters: + if (f['name'] == 'domain_id' and f['value'] is None): + f['value'] = NULL_DOMAIN_ID + + with sql.session_for_read() as session: query = session.query(RoleTable) refs = sql.filter_limit_query(RoleTable, query, hints) return [ref.to_dict() for ref in refs] @@ -35,7 +55,7 @@ class Role(assignment.RoleDriverV8): if not ids: return [] else: - with sql.transaction() as session: + with sql.session_for_read() as session: query = session.query(RoleTable) query = query.filter(RoleTable.id.in_(ids)) role_refs = query.all() @@ -48,12 +68,12 @@ class Role(assignment.RoleDriverV8): return ref def get_role(self, role_id): - with sql.transaction() as session: + with sql.session_for_read() 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: + with sql.session_for_write() as session: ref = self._get_role(session, role_id) old_dict = ref.to_dict() for k in role: @@ -66,15 +86,117 @@ class Role(assignment.RoleDriverV8): return ref.to_dict() def delete_role(self, role_id): - with sql.transaction() as session: + with sql.session_for_write() as session: ref = self._get_role(session, role_id) session.delete(ref) + def _get_implied_role(self, session, prior_role_id, implied_role_id): + query = session.query( + ImpliedRoleTable).filter( + ImpliedRoleTable.prior_role_id == prior_role_id).filter( + ImpliedRoleTable.implied_role_id == implied_role_id) + try: + ref = query.one() + except sql.NotFound: + raise exception.ImpliedRoleNotFound( + prior_role_id=prior_role_id, + implied_role_id=implied_role_id) + return ref + + @sql.handle_conflicts(conflict_type='implied_role') + def create_implied_role(self, prior_role_id, implied_role_id): + with sql.session_for_write() as session: + inference = {'prior_role_id': prior_role_id, + 'implied_role_id': implied_role_id} + ref = ImpliedRoleTable.from_dict(inference) + try: + session.add(ref) + except db_exception.DBReferenceError: + # We don't know which role threw this. + # Query each to trigger the exception. + self._get_role(session, prior_role_id) + self._get_role(session, implied_role_id) + return ref.to_dict() + + def delete_implied_role(self, prior_role_id, implied_role_id): + with sql.session_for_write() as session: + ref = self._get_implied_role(session, prior_role_id, + implied_role_id) + session.delete(ref) + + def list_implied_roles(self, prior_role_id): + with sql.session_for_read() as session: + query = session.query( + ImpliedRoleTable).filter( + ImpliedRoleTable.prior_role_id == prior_role_id) + refs = query.all() + return [ref.to_dict() for ref in refs] + + def list_role_inference_rules(self): + with sql.session_for_read() as session: + query = session.query(ImpliedRoleTable) + refs = query.all() + return [ref.to_dict() for ref in refs] + + def get_implied_role(self, prior_role_id, implied_role_id): + with sql.session_for_read() as session: + ref = self._get_implied_role(session, prior_role_id, + implied_role_id) + return ref.to_dict() + + +class ImpliedRoleTable(sql.ModelBase, sql.DictBase): + __tablename__ = 'implied_role' + attributes = ['prior_role_id', 'implied_role_id'] + prior_role_id = sql.Column( + sql.String(64), + sql.ForeignKey('role.id', ondelete="CASCADE"), + primary_key=True) + implied_role_id = sql.Column( + sql.String(64), + sql.ForeignKey('role.id', ondelete="CASCADE"), + primary_key=True) + + @classmethod + def from_dict(cls, dictionary): + new_dictionary = dictionary.copy() + return cls(**new_dictionary) + + def to_dict(self): + """Return a dictionary with model's attributes. + + overrides the `to_dict` function from the base class + to avoid having an `extra` field. + """ + d = dict() + for attr in self.__class__.attributes: + d[attr] = getattr(self, attr) + return d + class RoleTable(sql.ModelBase, sql.DictBase): + + def to_dict(self, include_extra_dict=False): + d = super(RoleTable, self).to_dict( + include_extra_dict=include_extra_dict) + if d['domain_id'] == NULL_DOMAIN_ID: + d['domain_id'] = None + return d + + @classmethod + def from_dict(cls, role_dict): + if 'domain_id' in role_dict and role_dict['domain_id'] is None: + new_dict = role_dict.copy() + new_dict['domain_id'] = NULL_DOMAIN_ID + else: + new_dict = role_dict + return super(RoleTable, cls).from_dict(new_dict) + __tablename__ = 'role' - attributes = ['id', 'name'] + attributes = ['id', 'name', 'domain_id'] id = sql.Column(sql.String(64), primary_key=True) - name = sql.Column(sql.String(255), unique=True, nullable=False) + name = sql.Column(sql.String(255), nullable=False) + domain_id = sql.Column(sql.String(64), nullable=False, + server_default=NULL_DOMAIN_ID) extra = sql.Column(sql.JsonBlob()) - __table_args__ = (sql.UniqueConstraint('name'), {}) + __table_args__ = (sql.UniqueConstraint('name', 'domain_id'),) |