"Fossies" - the Fresh Open Source Software Archive

Member "keystone-18.0.0/keystone/assignment/backends/sql.py" (14 Oct 2020, 17140 Bytes) of package /linux/misc/openstack/keystone-18.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: 17.0.0_vs_18.0.0.

    1 # Copyright 2012-13 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 keystone.assignment.backends import base
   16 from keystone.common import sql
   17 from keystone import exception
   18 from keystone.i18n import _
   19 
   20 
   21 class AssignmentType(object):
   22     USER_PROJECT = 'UserProject'
   23     GROUP_PROJECT = 'GroupProject'
   24     USER_DOMAIN = 'UserDomain'
   25     GROUP_DOMAIN = 'GroupDomain'
   26 
   27     @classmethod
   28     def calculate_type(cls, user_id, group_id, project_id, domain_id):
   29         if user_id:
   30             if project_id:
   31                 return cls.USER_PROJECT
   32             if domain_id:
   33                 return cls.USER_DOMAIN
   34         if group_id:
   35             if project_id:
   36                 return cls.GROUP_PROJECT
   37             if domain_id:
   38                 return cls.GROUP_DOMAIN
   39         # Invalid parameters combination
   40         raise exception.AssignmentTypeCalculationError(**locals())
   41 
   42 
   43 class Assignment(base.AssignmentDriverBase):
   44 
   45     @classmethod
   46     def default_role_driver(cls):
   47         return 'sql'
   48 
   49     def create_grant(self, role_id, user_id=None, group_id=None,
   50                      domain_id=None, project_id=None,
   51                      inherited_to_projects=False):
   52 
   53         assignment_type = AssignmentType.calculate_type(
   54             user_id, group_id, project_id, domain_id)
   55         try:
   56             with sql.session_for_write() as session:
   57                 session.add(RoleAssignment(
   58                     type=assignment_type,
   59                     actor_id=user_id or group_id,
   60                     target_id=project_id or domain_id,
   61                     role_id=role_id,
   62                     inherited=inherited_to_projects))
   63         except sql.DBDuplicateEntry:  # nosec : The v3 grant APIs are silent if
   64             # the assignment already exists
   65             pass
   66 
   67     def list_grant_role_ids(self, user_id=None, group_id=None,
   68                             domain_id=None, project_id=None,
   69                             inherited_to_projects=False):
   70         with sql.session_for_read() as session:
   71             q = session.query(RoleAssignment.role_id)
   72             q = q.filter(RoleAssignment.actor_id == (user_id or group_id))
   73             q = q.filter(RoleAssignment.target_id == (project_id or domain_id))
   74             q = q.filter(RoleAssignment.inherited == inherited_to_projects)
   75             return [x.role_id for x in q.all()]
   76 
   77     def _build_grant_filter(self, session, role_id, user_id, group_id,
   78                             domain_id, project_id, inherited_to_projects):
   79         q = session.query(RoleAssignment)
   80         q = q.filter_by(actor_id=user_id or group_id)
   81         if domain_id:
   82             q = q.filter_by(target_id=domain_id).filter(
   83                 (RoleAssignment.type == AssignmentType.USER_DOMAIN) |
   84                 (RoleAssignment.type == AssignmentType.GROUP_DOMAIN))
   85         else:
   86             q = q.filter_by(target_id=project_id).filter(
   87                 (RoleAssignment.type == AssignmentType.USER_PROJECT) |
   88                 (RoleAssignment.type == AssignmentType.GROUP_PROJECT))
   89         q = q.filter_by(role_id=role_id)
   90         q = q.filter_by(inherited=inherited_to_projects)
   91         return q
   92 
   93     def check_grant_role_id(self, role_id, user_id=None, group_id=None,
   94                             domain_id=None, project_id=None,
   95                             inherited_to_projects=False):
   96         with sql.session_for_read() as session:
   97             try:
   98                 q = self._build_grant_filter(
   99                     session, role_id, user_id, group_id, domain_id, project_id,
  100                     inherited_to_projects)
  101                 q.one()
  102             except sql.NotFound:
  103                 actor_id = user_id or group_id
  104                 target_id = domain_id or project_id
  105                 raise exception.RoleAssignmentNotFound(role_id=role_id,
  106                                                        actor_id=actor_id,
  107                                                        target_id=target_id)
  108 
  109     def delete_grant(self, role_id, user_id=None, group_id=None,
  110                      domain_id=None, project_id=None,
  111                      inherited_to_projects=False):
  112         with sql.session_for_write() as session:
  113             q = self._build_grant_filter(
  114                 session, role_id, user_id, group_id, domain_id, project_id,
  115                 inherited_to_projects)
  116             if not q.delete(False):
  117                 actor_id = user_id or group_id
  118                 target_id = domain_id or project_id
  119                 raise exception.RoleAssignmentNotFound(role_id=role_id,
  120                                                        actor_id=actor_id,
  121                                                        target_id=target_id)
  122 
  123     def add_role_to_user_and_project(self, user_id, project_id, role_id):
  124         try:
  125             with sql.session_for_write() as session:
  126                 session.add(RoleAssignment(
  127                     type=AssignmentType.USER_PROJECT,
  128                     actor_id=user_id, target_id=project_id,
  129                     role_id=role_id, inherited=False))
  130         except sql.DBDuplicateEntry:
  131             msg = ('User %s already has role %s in tenant %s'
  132                    % (user_id, role_id, project_id))
  133             raise exception.Conflict(type='role grant', details=msg)
  134 
  135     def remove_role_from_user_and_project(self, user_id, project_id, role_id):
  136         with sql.session_for_write() as session:
  137             q = session.query(RoleAssignment)
  138             q = q.filter_by(actor_id=user_id)
  139             q = q.filter_by(target_id=project_id)
  140             q = q.filter_by(role_id=role_id)
  141             if q.delete() == 0:
  142                 raise exception.RoleNotFound(message=_(
  143                     'Cannot remove role that has not been granted, %s') %
  144                     role_id)
  145 
  146     def _get_user_assignment_types(self):
  147         return [AssignmentType.USER_PROJECT, AssignmentType.USER_DOMAIN]
  148 
  149     def _get_group_assignment_types(self):
  150         return [AssignmentType.GROUP_PROJECT, AssignmentType.GROUP_DOMAIN]
  151 
  152     def _get_project_assignment_types(self):
  153         return [AssignmentType.USER_PROJECT, AssignmentType.GROUP_PROJECT]
  154 
  155     def _get_domain_assignment_types(self):
  156         return [AssignmentType.USER_DOMAIN, AssignmentType.GROUP_DOMAIN]
  157 
  158     def _get_assignment_types(self, user, group, project, domain):
  159         """Return a list of role assignment types based on provided entities.
  160 
  161         If one of user or group (the "actor") as well as one of project or
  162         domain (the "target") are provided, the list will contain the role
  163         assignment type for that specific pair of actor and target.
  164 
  165         If only an actor or target is provided, the list will contain the
  166         role assignment types that satisfy the specified entity.
  167 
  168         For example, if user and project are provided, the return will be:
  169 
  170             [AssignmentType.USER_PROJECT]
  171 
  172         However, if only user was provided, the return would be:
  173 
  174             [AssignmentType.USER_PROJECT, AssignmentType.USER_DOMAIN]
  175 
  176         It is not expected that user and group (or project and domain) are
  177         specified - but if they are, the most fine-grained value will be
  178         chosen (i.e. user over group, project over domain).
  179 
  180         """
  181         actor_types = []
  182         if user:
  183             actor_types = self._get_user_assignment_types()
  184         elif group:
  185             actor_types = self._get_group_assignment_types()
  186 
  187         target_types = []
  188         if project:
  189             target_types = self._get_project_assignment_types()
  190         elif domain:
  191             target_types = self._get_domain_assignment_types()
  192 
  193         if actor_types and target_types:
  194             return list(set(actor_types).intersection(target_types))
  195 
  196         return actor_types or target_types
  197 
  198     def list_role_assignments(self, role_id=None,
  199                               user_id=None, group_ids=None,
  200                               domain_id=None, project_ids=None,
  201                               inherited_to_projects=None):
  202 
  203         def denormalize_role(ref):
  204             assignment = {}
  205             if ref.type == AssignmentType.USER_PROJECT:
  206                 assignment['user_id'] = ref.actor_id
  207                 assignment['project_id'] = ref.target_id
  208             elif ref.type == AssignmentType.USER_DOMAIN:
  209                 assignment['user_id'] = ref.actor_id
  210                 assignment['domain_id'] = ref.target_id
  211             elif ref.type == AssignmentType.GROUP_PROJECT:
  212                 assignment['group_id'] = ref.actor_id
  213                 assignment['project_id'] = ref.target_id
  214             elif ref.type == AssignmentType.GROUP_DOMAIN:
  215                 assignment['group_id'] = ref.actor_id
  216                 assignment['domain_id'] = ref.target_id
  217             else:
  218                 raise exception.Error(message=_(
  219                     'Unexpected assignment type encountered, %s') %
  220                     ref.type)
  221             assignment['role_id'] = ref.role_id
  222             if ref.inherited:
  223                 assignment['inherited_to_projects'] = 'projects'
  224             return assignment
  225 
  226         with sql.session_for_read() as session:
  227             assignment_types = self._get_assignment_types(
  228                 user_id, group_ids, project_ids, domain_id)
  229 
  230             targets = None
  231             if project_ids:
  232                 targets = project_ids
  233             elif domain_id:
  234                 targets = [domain_id]
  235 
  236             actors = None
  237             if group_ids:
  238                 actors = group_ids
  239             elif user_id:
  240                 actors = [user_id]
  241 
  242             query = session.query(RoleAssignment)
  243 
  244             if role_id:
  245                 query = query.filter_by(role_id=role_id)
  246             if actors:
  247                 query = query.filter(RoleAssignment.actor_id.in_(actors))
  248             if targets:
  249                 query = query.filter(RoleAssignment.target_id.in_(targets))
  250             if assignment_types:
  251                 query = query.filter(RoleAssignment.type.in_(assignment_types))
  252             if inherited_to_projects is not None:
  253                 query = query.filter_by(inherited=inherited_to_projects)
  254 
  255             return [denormalize_role(ref) for ref in query.all()]
  256 
  257     def delete_project_assignments(self, project_id):
  258         with sql.session_for_write() as session:
  259             q = session.query(RoleAssignment)
  260             q = q.filter_by(target_id=project_id).filter(
  261                 RoleAssignment.type.in_((AssignmentType.USER_PROJECT,
  262                                          AssignmentType.GROUP_PROJECT))
  263             )
  264             q.delete(False)
  265 
  266     def delete_role_assignments(self, role_id):
  267         with sql.session_for_write() as session:
  268             q = session.query(RoleAssignment)
  269             q = q.filter_by(role_id=role_id)
  270             q.delete(False)
  271 
  272     def delete_domain_assignments(self, domain_id):
  273         with sql.session_for_write() as session:
  274             q = session.query(RoleAssignment)
  275             q = q.filter(RoleAssignment.target_id == domain_id).filter(
  276                 (RoleAssignment.type == AssignmentType.USER_DOMAIN) |
  277                 (RoleAssignment.type == AssignmentType.GROUP_DOMAIN))
  278             q.delete(False)
  279 
  280     def delete_user_assignments(self, user_id):
  281         with sql.session_for_write() as session:
  282             q = session.query(RoleAssignment)
  283             q = q.filter_by(actor_id=user_id).filter(
  284                 RoleAssignment.type.in_((AssignmentType.USER_PROJECT,
  285                                          AssignmentType.USER_DOMAIN))
  286             )
  287             q.delete(False)
  288 
  289     def delete_group_assignments(self, group_id):
  290         with sql.session_for_write() as session:
  291             q = session.query(RoleAssignment)
  292             q = q.filter_by(actor_id=group_id).filter(
  293                 RoleAssignment.type.in_((AssignmentType.GROUP_PROJECT,
  294                                          AssignmentType.GROUP_DOMAIN))
  295             )
  296             q.delete(False)
  297 
  298     def create_system_grant(self, role_id, actor_id, target_id,
  299                             assignment_type, inherited):
  300         try:
  301             with sql.session_for_write() as session:
  302                 session.add(
  303                     SystemRoleAssignment(
  304                         type=assignment_type,
  305                         actor_id=actor_id,
  306                         target_id=target_id,
  307                         role_id=role_id,
  308                         inherited=inherited
  309                     )
  310                 )
  311         except sql.DBDuplicateEntry:  # nosec : The v3 grant APIs are silent if
  312             # the assignment already exists
  313             pass
  314 
  315     def list_system_grants(self, actor_id, target_id, assignment_type):
  316         with sql.session_for_read() as session:
  317             query = session.query(SystemRoleAssignment)
  318             if actor_id:
  319                 query = query.filter_by(actor_id=actor_id)
  320             if target_id:
  321                 query = query.filter_by(target_id=target_id)
  322             if assignment_type:
  323                 query = query.filter_by(type=assignment_type)
  324             results = query.all()
  325 
  326         return [role.to_dict() for role in results]
  327 
  328     def list_system_grants_by_role(self, role_id):
  329         with sql.session_for_read() as session:
  330             query = session.query(SystemRoleAssignment)
  331             query = query.filter_by(role_id=role_id)
  332             return query.all()
  333 
  334     def check_system_grant(self, role_id, actor_id, target_id, inherited):
  335         with sql.session_for_read() as session:
  336             try:
  337                 q = session.query(SystemRoleAssignment)
  338                 q = q.filter_by(actor_id=actor_id)
  339                 q = q.filter_by(target_id=target_id)
  340                 q = q.filter_by(role_id=role_id)
  341                 q = q.filter_by(inherited=inherited)
  342                 q.one()
  343             except sql.NotFound:
  344                 raise exception.RoleAssignmentNotFound(
  345                     role_id=role_id, actor_id=actor_id, target_id=target_id
  346                 )
  347 
  348     def delete_system_grant(self, role_id, actor_id, target_id, inherited):
  349         with sql.session_for_write() as session:
  350             q = session.query(SystemRoleAssignment)
  351             q = q.filter_by(actor_id=actor_id)
  352             q = q.filter_by(target_id=target_id)
  353             q = q.filter_by(role_id=role_id)
  354             q = q.filter_by(inherited=inherited)
  355             if not q.delete(False):
  356                 raise exception.RoleAssignmentNotFound(
  357                     role_id=role_id, actor_id=actor_id, target_id=target_id
  358                 )
  359 
  360 
  361 class RoleAssignment(sql.ModelBase, sql.ModelDictMixin):
  362     __tablename__ = 'assignment'
  363     attributes = ['type', 'actor_id', 'target_id', 'role_id', 'inherited']
  364     # NOTE(henry-nash): Postgres requires a name to be defined for an Enum
  365     type = sql.Column(
  366         sql.Enum(AssignmentType.USER_PROJECT, AssignmentType.GROUP_PROJECT,
  367                  AssignmentType.USER_DOMAIN, AssignmentType.GROUP_DOMAIN,
  368                  name='type'),
  369         nullable=False)
  370     actor_id = sql.Column(sql.String(64), nullable=False)
  371     target_id = sql.Column(sql.String(64), nullable=False)
  372     role_id = sql.Column(sql.String(64), nullable=False)
  373     inherited = sql.Column(sql.Boolean, default=False, nullable=False)
  374     __table_args__ = (
  375         sql.PrimaryKeyConstraint('type', 'actor_id', 'target_id', 'role_id',
  376                                  'inherited'),
  377         sql.Index('ix_actor_id', 'actor_id'),
  378     )
  379 
  380     def to_dict(self):
  381         """Override parent method with a simpler implementation.
  382 
  383         RoleAssignment doesn't have non-indexed 'extra' attributes, so the
  384         parent implementation is not applicable.
  385         """
  386         return dict(self.items())
  387 
  388 
  389 class SystemRoleAssignment(sql.ModelBase, sql.ModelDictMixin):
  390     __tablename__ = 'system_assignment'
  391     attributes = ['type', 'actor_id', 'target_id', 'role_id', 'inherited']
  392     type = sql.Column(sql.String(64), nullable=False)
  393     actor_id = sql.Column(sql.String(64), nullable=False)
  394     target_id = sql.Column(sql.String(64), nullable=False)
  395     role_id = sql.Column(sql.String(64), nullable=False)
  396     inherited = sql.Column(sql.Boolean, default=False, nullable=False)
  397     __table_args__ = (
  398         sql.PrimaryKeyConstraint('type', 'actor_id', 'target_id', 'role_id',
  399                                  'inherited'),
  400         sql.Index('ix_system_actor_id', 'actor_id'),
  401     )
  402 
  403     def to_dict(self):
  404         """Override parent method with a simpler implementation.
  405 
  406         RoleAssignment doesn't have non-indexed 'extra' attributes, so the
  407         parent implementation is not applicable.
  408         """
  409         return dict(self.items())