"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/trust/backends/sql.py" (13 May 2020, 9685 Bytes) of package /linux/misc/openstack/keystone-17.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "sql.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.1_vs_17.0.0.

    1 # Copyright 2012 OpenStack Foundation
    2 #
    3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 # not use this file except in compliance with the License. You may obtain
    5 # a copy of the License at
    6 #
    7 #      http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 # Unless required by applicable law or agreed to in writing, software
   10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 # License for the specific language governing permissions and limitations
   13 # under the License.
   14 
   15 from oslo_utils import timeutils
   16 
   17 import sqlalchemy
   18 from sqlalchemy.ext.hybrid import hybrid_property
   19 
   20 from keystone.common import sql
   21 from keystone import exception
   22 from keystone.trust.backends import base
   23 
   24 
   25 # The maximum number of iterations that will be attempted for optimistic
   26 # locking on consuming a limited-use trust.
   27 MAXIMUM_CONSUME_ATTEMPTS = 10
   28 
   29 
   30 class TrustModel(sql.ModelBase, sql.ModelDictMixinWithExtras):
   31     __tablename__ = 'trust'
   32     attributes = ['id', 'trustor_user_id', 'trustee_user_id',
   33                   'project_id', 'impersonation', 'expires_at',
   34                   'remaining_uses', 'deleted_at', 'redelegated_trust_id',
   35                   'redelegation_count']
   36     id = sql.Column(sql.String(64), primary_key=True)
   37     # user id of owner
   38     trustor_user_id = sql.Column(sql.String(64), nullable=False,)
   39     # user_id of user allowed to consume this preauth
   40     trustee_user_id = sql.Column(sql.String(64), nullable=False)
   41     project_id = sql.Column(sql.String(64))
   42     impersonation = sql.Column(sql.Boolean, nullable=False)
   43     deleted_at = sql.Column(sql.DateTime)
   44     _expires_at = sql.Column('expires_at', sql.DateTime)
   45     expires_at_int = sql.Column(sql.DateTimeInt(), nullable=True)
   46     remaining_uses = sql.Column(sql.Integer, nullable=True)
   47     redelegated_trust_id = sql.Column(sql.String(64), nullable=True)
   48     redelegation_count = sql.Column(sql.Integer, nullable=True)
   49     extra = sql.Column(sql.JsonBlob())
   50     __table_args__ = (sql.UniqueConstraint(
   51                       'trustor_user_id', 'trustee_user_id', 'project_id',
   52                       'impersonation', 'expires_at',
   53                       name='duplicate_trust_constraint'),)
   54 
   55     @hybrid_property
   56     def expires_at(self):
   57         return self.expires_at_int or self._expires_at
   58 
   59     @expires_at.setter
   60     def expires_at(self, value):
   61         self._expires_at = value
   62         self.expires_at_int = value
   63 
   64 
   65 class TrustRole(sql.ModelBase):
   66     __tablename__ = 'trust_role'
   67     attributes = ['trust_id', 'role_id']
   68     trust_id = sql.Column(sql.String(64), primary_key=True, nullable=False)
   69     role_id = sql.Column(sql.String(64), primary_key=True, nullable=False)
   70 
   71 
   72 class Trust(base.TrustDriverBase):
   73     @sql.handle_conflicts(conflict_type='trust')
   74     def create_trust(self, trust_id, trust, roles):
   75         with sql.session_for_write() as session:
   76             ref = TrustModel.from_dict(trust)
   77             ref['id'] = trust_id
   78             if ref.get('expires_at') and ref['expires_at'].tzinfo is not None:
   79                 ref['expires_at'] = timeutils.normalize_time(ref['expires_at'])
   80             session.add(ref)
   81             added_roles = []
   82             for role in roles:
   83                 trust_role = TrustRole()
   84                 trust_role.trust_id = trust_id
   85                 trust_role.role_id = role['id']
   86                 added_roles.append({'id': role['id']})
   87                 session.add(trust_role)
   88             trust_dict = ref.to_dict()
   89             trust_dict['roles'] = added_roles
   90             return trust_dict
   91 
   92     def _add_roles(self, trust_id, session, trust_dict):
   93         roles = []
   94         for role in session.query(TrustRole).filter_by(trust_id=trust_id):
   95             roles.append({'id': role.role_id})
   96         trust_dict['roles'] = roles
   97 
   98     def consume_use(self, trust_id):
   99         for attempt in range(MAXIMUM_CONSUME_ATTEMPTS):
  100             with sql.session_for_write() as session:
  101                 try:
  102                     query_result = (session.query(TrustModel.remaining_uses).
  103                                     filter_by(id=trust_id).
  104                                     filter_by(deleted_at=None).one())
  105                 except sql.NotFound:
  106                     raise exception.TrustNotFound(trust_id=trust_id)
  107 
  108                 remaining_uses = query_result.remaining_uses
  109 
  110                 if remaining_uses is None:
  111                     # unlimited uses, do nothing
  112                     break
  113                 elif remaining_uses > 0:
  114                     # NOTE(morganfainberg): use an optimistic locking method
  115                     # to ensure we only ever update a trust that has the
  116                     # expected number of remaining uses.
  117                     rows_affected = (
  118                         session.query(TrustModel).
  119                         filter_by(id=trust_id).
  120                         filter_by(deleted_at=None).
  121                         filter_by(remaining_uses=remaining_uses).
  122                         update({'remaining_uses': (remaining_uses - 1)},
  123                                synchronize_session=False))
  124                     if rows_affected == 1:
  125                         # Successfully consumed a single limited-use trust.
  126                         # Since trust_id is the PK on the Trust table, there is
  127                         # no case we should match more than 1 row in the
  128                         # update. We either update 1 row or 0 rows.
  129                         break
  130                 else:
  131                     raise exception.TrustUseLimitReached(trust_id=trust_id)
  132         else:
  133             # NOTE(morganfainberg): In the case the for loop is not prematurely
  134             # broken out of, this else block is executed. This means the trust
  135             # was not unlimited nor was it consumed (we hit the maximum
  136             # iteration limit). This is just an indicator that we were unable
  137             # to get the optimistic lock rather than silently failing or
  138             # incorrectly indicating a trust was consumed.
  139             raise exception.TrustConsumeMaximumAttempt(trust_id=trust_id)
  140 
  141     def get_trust(self, trust_id, deleted=False):
  142         with sql.session_for_read() as session:
  143             query = session.query(TrustModel).filter_by(id=trust_id)
  144             if not deleted:
  145                 query = query.filter_by(deleted_at=None)
  146             ref = query.first()
  147             if ref is None:
  148                 raise exception.TrustNotFound(trust_id=trust_id)
  149             if ref.expires_at is not None and not deleted:
  150                 now = timeutils.utcnow()
  151                 if now > ref.expires_at:
  152                     raise exception.TrustNotFound(trust_id=trust_id)
  153             # Do not return trusts that can't be used anymore
  154             if ref.remaining_uses is not None and not deleted:
  155                 if ref.remaining_uses <= 0:
  156                     raise exception.TrustNotFound(trust_id=trust_id)
  157             trust_dict = ref.to_dict()
  158 
  159             self._add_roles(trust_id, session, trust_dict)
  160             return trust_dict
  161 
  162     def list_trusts(self):
  163         with sql.session_for_read() as session:
  164             trusts = session.query(TrustModel).filter_by(deleted_at=None)
  165             return [trust_ref.to_dict() for trust_ref in trusts]
  166 
  167     def list_trusts_for_trustee(self, trustee_user_id):
  168         with sql.session_for_read() as session:
  169             trusts = (session.query(TrustModel).
  170                       filter_by(deleted_at=None).
  171                       filter_by(trustee_user_id=trustee_user_id))
  172             return [trust_ref.to_dict() for trust_ref in trusts]
  173 
  174     def list_trusts_for_trustor(self, trustor_user_id):
  175         with sql.session_for_read() as session:
  176             trusts = (session.query(TrustModel).
  177                       filter_by(deleted_at=None).
  178                       filter_by(trustor_user_id=trustor_user_id))
  179             return [trust_ref.to_dict() for trust_ref in trusts]
  180 
  181     @sql.handle_conflicts(conflict_type='trust')
  182     def delete_trust(self, trust_id):
  183         with sql.session_for_write() as session:
  184             trust_ref = session.query(TrustModel).get(trust_id)
  185             if not trust_ref:
  186                 raise exception.TrustNotFound(trust_id=trust_id)
  187             trust_ref.deleted_at = timeutils.utcnow()
  188 
  189     def delete_trusts_for_project(self, project_id):
  190         with sql.session_for_write() as session:
  191             query = session.query(TrustModel)
  192             trusts = query.filter_by(project_id=project_id)
  193             for trust_ref in trusts:
  194                 trust_ref.deleted_at = timeutils.utcnow()
  195 
  196     def flush_expired_and_soft_deleted_trusts(self, project_id=None,
  197                                               trustor_user_id=None,
  198                                               trustee_user_id=None,
  199                                               date=None):
  200         with sql.session_for_write() as session:
  201             query = session.query(TrustModel)
  202             query = query.\
  203                 filter(sqlalchemy.or_(TrustModel.deleted_at.isnot(None),
  204                                       sqlalchemy.and_(
  205                                           TrustModel.expires_at.isnot(None),
  206                                           TrustModel.expires_at < date)))
  207             if project_id:
  208                 query = query.filter_by(project_id=project_id)
  209             if trustor_user_id:
  210                 query = query.filter_by(trustor_user_id=trustor_user_id)
  211             if trustee_user_id:
  212                 query = query.filter_by(trustee_user_id=trustee_user_id)
  213             query.delete(synchronize_session=False)
  214             session.flush()