# keystone.trust.backends.sql

# Source code for keystone.trust.backends.sql

    # Copyright 2012 OpenStack Foundation
    #
    # 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 oslo_utils import timeutils
    import sqlalchemy
    from sqlalchemy.ext.hybrid import hybrid_property

    from keystone.common import sql
    from keystone import exception
    from keystone.trust.backends import base

    # The maximum number of iterations that will be attempted for optimistic
    # locking on consuming a limited-use trust.
    MAXIMUM_CONSUME_ATTEMPTS = 10

    class TrustModel(sql.ModelBase, sql.ModelDictMixinWithExtras):
        __tablename__ = 'trust'
        attributes = [
            'id',
            'trustor_user_id',
            'trustee_user_id',
            'project_id',
            'impersonation',
            'expires_at',
            'remaining_uses',
            'deleted_at',
            'redelegated_trust_id',
            'redelegation_count',
        ]
        id = sql.Column(sql.String(64), primary_key=True)
        # user id of owner
        trustor_user_id = sql.Column(sql.String(64), nullable=False)
        # user_id of user allowed to consume this preauth
        trustee_user_id = sql.Column(sql.String(64), nullable=False)
        project_id = sql.Column(sql.String(64))
        impersonation = sql.Column(sql.Boolean, nullable=False)
        deleted_at = sql.Column(sql.DateTime)
        _expires_at = sql.Column('expires_at', sql.DateTime)
        expires_at_int = sql.Column(sql.DateTimeInt(), nullable=True)
        remaining_uses = sql.Column(sql.Integer, nullable=True)
        redelegated_trust_id = sql.Column(sql.String(64), nullable=True)
        redelegation_count = sql.Column(sql.Integer, nullable=True)
        extra = sql.Column(sql.JsonBlob())
        __table_args__ = (
            sql.UniqueConstraint(
                'trustor_user_id',
                'trustee_user_id',
                'project_id',
                'impersonation',
                'expires_at',
                name='duplicate_trust_constraint',
            ),
        )

        @hybrid_property
        def expires_at(self):
            return self.expires_at_int or self._expires_at

        @expires_at.setter  # type: ignore[no-redef]
        def expires_at(self, value):
            self._expires_at = value
            self.expires_at_int = value

    class TrustRole(sql.ModelBase):
        __tablename__ = 'trust_role'
        attributes = ['trust_id', 'role_id']
        trust_id = sql.Column(sql.String(64), primary_key=True, nullable=False)
        role_id = sql.Column(sql.String(64), primary_key=True, nullable=False)

    class Trust(base.TrustDriverBase):

        @sql.handle_conflicts(conflict_type='trust')
        def create_trust(self, trust_id, trust, roles):
            with sql.session_for_write() as session:
                ref = TrustModel.from_dict(trust)
                ref['id'] = trust_id
                if ref.get('expires_at') and ref['expires_at'].tzinfo is not None:
                    ref['expires_at'] = timeutils.normalize_time(ref['expires_at'])
                session.add(ref)
                added_roles = []
                for role in roles:
                    trust_role = TrustRole()
                    trust_role.trust_id = trust_id
                    trust_role.role_id = role['id']
                    added_roles.append({'id': role['id']})
                    session.add(trust_role)
                trust_dict = ref.to_dict()
                trust_dict['roles'] = added_roles
                return trust_dict

        def _add_roles(self, trust_id, session, trust_dict):
            roles = []
            for role in session.query(TrustRole).filter_by(trust_id=trust_id):
                roles.append({'id': role.role_id})
            trust_dict['roles'] = roles

        def consume_use(self, trust_id):
            for attempt in range(MAXIMUM_CONSUME_ATTEMPTS):
                with sql.session_for_write() as session:
                    try:
                        query_result = (
                            session.query(TrustModel.remaining_uses)
                            .filter_by(id=trust_id)
                            .filter_by(deleted_at=None)
                            .one()
                        )
                    except sql.NotFound:
                        raise exception.TrustNotFound(trust_id=trust_id)

                    remaining_uses = query_result.remaining_uses

                    if remaining_uses is None:
                        # unlimited uses, do nothing
                        break
                    elif remaining_uses > 0:
                        # NOTE(morganfainberg): use an optimistic locking method
                        # to ensure we only ever update a trust that has the
                        # expected number of remaining uses.
                        rows_affected = (
                            session.query(TrustModel)
                            .filter_by(id=trust_id)
                            .filter_by(deleted_at=None)
                            .filter_by(remaining_uses=remaining_uses)
                            .update(
                                {'remaining_uses': (remaining_uses - 1)},
                                synchronize_session=False,
                            )
                        )
                        if rows_affected == 1:
                            # Successfully consumed a single limited-use trust.
                            # Since trust_id is the PK on the Trust table, there is
                            # no case we should match more than 1 row in the
                            # update. We either update 1 row or 0 rows.
                            break
                    else:
                        raise exception.TrustUseLimitReached(trust_id=trust_id)
            else:
                # NOTE(morganfainberg): In the case the for loop is not prematurely
                # broken out of, this else block is executed. This means the trust
                # was not unlimited nor was it consumed (we hit the maximum
                # iteration limit). This is just an indicator that we were unable
                # to get the optimistic lock rather than silently failing or
                # incorrectly indicating a trust was consumed.
                raise exception.TrustConsumeMaximumAttempt(trust_id=trust_id)

        def get_trust(self, trust_id, deleted=False):
            with sql.session_for_read() as session:
                query = session.query(TrustModel).filter_by(id=trust_id)
                if not deleted:
                    query = query.filter_by(deleted_at=None)
                ref = query.first()
                if ref is None:
                    raise exception.TrustNotFound(trust_id=trust_id)
                if ref.expires_at is not None and not deleted:
                    now = timeutils.utcnow()
                    if now > ref.expires_at:
                        raise exception.TrustNotFound(trust_id=trust_id)
                # Do not return trusts that can't be used anymore
                if ref.remaining_uses is not None and not deleted:
                    if ref.remaining_uses <= 0:
                        raise exception.TrustNotFound(trust_id=trust_id)
                trust_dict = ref.to_dict()

                self._add_roles(trust_id, session, trust_dict)
                return trust_dict

        def list_trusts(self):
            with sql.session_for_read() as session:
                trusts = session.query(TrustModel).filter_by(deleted_at=None)
                return [trust_ref.to_dict() for trust_ref in trusts]

        def list_trusts_for_trustee(self, trustee_user_id):
            with sql.session_for_read() as session:
                trusts = (
                    session.query(TrustModel)
                    .filter_by(deleted_at=None)
                    .filter_by(trustee_user_id=trustee_user_id)
                )
                return [trust_ref.to_dict() for trust_ref in trusts]

        def list_trusts_for_trustor(
            self, trustor_user_id, redelegated_trust_id=None
        ):
            with sql.session_for_read() as session:
                trusts = (
                    session.query(TrustModel)
                    .filter_by(deleted_at=None)
                    .filter_by(trustor_user_id=trustor_user_id)
                )
                if redelegated_trust_id:
                    trusts = trusts.filter_by(
                        redelegated_trust_id=redelegated_trust_id
                    )
                return [trust_ref.to_dict() for trust_ref in trusts]

        @sql.handle_conflicts(conflict_type='trust')
        def delete_trust(self, trust_id):
            with sql.session_for_write() as session:
                trust_ref = session.get(TrustModel, trust_id)
                if not trust_ref:
                    raise exception.TrustNotFound(trust_id=trust_id)
                trust_ref.deleted_at = timeutils.utcnow()

        def delete_trusts_for_project(self, project_id):
            with sql.session_for_write() as session:
                query = session.query(TrustModel)
                trusts = query.filter_by(project_id=project_id)
                for trust_ref in trusts:
                    trust_ref.deleted_at = timeutils.utcnow()

        def flush_expired_and_soft_deleted_trusts(
            self,
            project_id=None,
            trustor_user_id=None,
            trustee_user_id=None,
            date=None,
        ):
            with sql.session_for_write() as session:
                query = session.query(TrustModel)
                query = query.filter(
                    sqlalchemy.or_(
                        TrustModel.deleted_at.isnot(None),
                        sqlalchemy.and_(
                            TrustModel.expires_at.isnot(None),
                            TrustModel.expires_at < date,
                        ),
                    )
                )
                if project_id:
                    query = query.filter_by(project_id=project_id)
                if trustor_user_id:
                    query = query.filter_by(trustor_user_id=trustor_user_id)
                if trustee_user_id:
                    query = query.filter_by(trustee_user_id=trustee_user_id)
                query.delete(synchronize_session=False)
                session.flush()

this page last updated: 2026-06-11 21:50:20

Except where otherwise noted, this document is licensed under Creative Commons Attribution 3.0 License. See all OpenStack Legal Documents.

found an error? report a bug