# keystone.revoke.backends.sql

# Source code for keystone.revoke.backends.sql

    # 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_db import api as oslo_db_api
    import sqlalchemy

    from keystone.common import sql
    from keystone.models import revoke_model
    from keystone.revoke.backends import base

    class RevocationEvent(sql.ModelBase, sql.ModelDictMixin):
        __tablename__ = 'revocation_event'
        attributes = revoke_model.REVOKE_KEYS

        # The id field is not going to be exposed to the outside world.
        # It is, however, necessary for SQLAlchemy.
        id = sql.Column(sql.Integer, primary_key=True, nullable=False)
        domain_id = sql.Column(sql.String(64))
        project_id = sql.Column(sql.String(64))
        user_id = sql.Column(sql.String(64))
        role_id = sql.Column(sql.String(64))
        trust_id = sql.Column(sql.String(64))
        consumer_id = sql.Column(sql.String(64))
        access_token_id = sql.Column(sql.String(64))
        issued_before = sql.Column(sql.DateTime(), nullable=False, index=True)
        expires_at = sql.Column(sql.DateTime())
        revoked_at = sql.Column(sql.DateTime(), nullable=False, index=True)
        audit_id = sql.Column(sql.String(32))
        audit_chain_id = sql.Column(sql.String(32))
        __table_args__ = (
            sql.Index(
                'ix_revocation_event_project_id_issued_before',
                'project_id',
                'issued_before',
            ),
            sql.Index(
                'ix_revocation_event_user_id_issued_before',
                'user_id',
                'issued_before',
            ),
            sql.Index(
                'ix_revocation_event_audit_id_issued_before',
                'audit_id',
                'issued_before',
            ),
            sql.Index(
                'ix_revocation_event_project_id_user_id', 'project_id', 'user_id'
            ),
            sql.Index(
                'ix_revocation_event_composite',
                'issued_before',
                'user_id',
                'project_id',
                'audit_id',
            ),
        )

    class Revoke(base.RevokeDriverBase):
        def _flush_batch_size(self, dialect):
            batch_size = 0
            if dialect == 'ibm_db_sa':
                # This functionality is limited to DB2, because
                # it is necessary to prevent the transaction log
                # from filling up, whereas at least some of the
                # other supported databases do not support update
                # queries with LIMIT subqueries nor do they appear
                # to require the use of such queries when deleting
                # large numbers of records at once.
                batch_size = 100
                # Limit of 100 is known to not fill a transaction log
                # of default maximum size while not significantly
                # impacting the performance of large token purges on
                # systems where the maximum transaction log size has
                # been increased beyond the default.
            return batch_size

        def _prune_expired_events(self):
            oldest = base.revoked_before_cutoff_time()

            with sql.session_for_write() as session:
                dialect = session.bind.dialect.name
                batch_size = self._flush_batch_size(dialect)
                if batch_size > 0:
                    query = session.query(RevocationEvent.id)
                    query = query.filter(RevocationEvent.revoked_at < oldest)
                    query = query.limit(batch_size).subquery()
                    delete_query = session.query(RevocationEvent).filter(
                        RevocationEvent.id.in_(query)
                    )
                    while True:
                        rowcount = delete_query.delete(synchronize_session=False)
                        if rowcount == 0:
                            break
                else:
                    query = session.query(RevocationEvent)
                    query = query.filter(RevocationEvent.revoked_at < oldest)
                    query.delete(synchronize_session=False)

                session.flush()

        def _list_token_events(self, token):
            with sql.session_for_read() as session:
                query = session.query(RevocationEvent).filter(
                    RevocationEvent.issued_before >= token['issued_at']
                )
                user = [RevocationEvent.user_id.is_(None)]
                proj = [RevocationEvent.project_id.is_(None)]
                audit = [RevocationEvent.audit_id.is_(None)]
                trust = [RevocationEvent.trust_id.is_(None)]
                if token['user_id']:
                    user.append(RevocationEvent.user_id == token['user_id'])
                if token['trustor_id']:
                    user.append(RevocationEvent.user_id == token['trustor_id'])
                if token['trustee_id']:
                    user.append(RevocationEvent.user_id == token['trustee_id'])
                if token['project_id']:
                    proj.append(RevocationEvent.project_id == token['project_id'])
                if token['audit_id']:
                    audit.append(RevocationEvent.audit_id == token['audit_id'])
                if token['trust_id']:
                    trust.append(RevocationEvent.trust_id == token['trust_id'])
                query = query.filter(
                    sqlalchemy.and_(
                        sqlalchemy.or_(*user),
                        sqlalchemy.or_(*proj),
                        sqlalchemy.or_(*audit),
                        sqlalchemy.or_(*trust),
                    )
                )
                events = [revoke_model.RevokeEvent(**e.to_dict()) for e in query]
                return events

        def _list_last_fetch_events(self, last_fetch=None):
            with sql.session_for_read() as session:
                query = session.query(RevocationEvent).order_by(
                    RevocationEvent.revoked_at
                )

                if last_fetch:
                    query = query.filter(RevocationEvent.revoked_at > last_fetch)

                events = [revoke_model.RevokeEvent(**e.to_dict()) for e in query]
                return events

        def list_events(self, last_fetch=None, token=None):
            if token:
                return self._list_token_events(token)
            else:
                return self._list_last_fetch_events(last_fetch)

        @oslo_db_api.wrap_db_retry(retry_on_deadlock=True)
        def revoke(self, event):
            kwargs = {}
            for attr in revoke_model.REVOKE_KEYS:
                kwargs[attr] = getattr(event, attr)
            record = RevocationEvent(**kwargs)
            with sql.session_for_write() as session:
                session.add(record)
                self._prune_expired_events()

this page last updated: 2026-04-01 20:09:17

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