示例#1
0
class Roleable(object):
    """Roleable Mixin

  Mixin that adds access_control_list property to the parent object. Access
  control list includes a list of AccessControlList objects.
  """

    # pylint: disable=not-an-iterable
    # this pylint disable rule is here because of multiple usages of
    # _access_control_list in this file which gives off a false warning.

    _update_raw = [
        'access_control_list',
    ]
    _fulltext_attrs = [
        CustomRoleAttr('access_control_list'),
    ]
    _api_attrs = reflection.ApiAttributes(
        reflection.Attribute('access_control_list', True, True, True))
    MAX_ASSIGNEE_NUM = 1
    MAX_VERIFIER_NUM = 1

    _custom_publish = {
        'access_control_list': lambda obj: obj.acl_json,
    }

    def __init__(self, *args, **kwargs):
        for ac_role in role.get_ac_roles_for(self.type).values():
            AccessControlList(
                object=self,
                ac_role=ac_role,
            )
        super(Roleable, self).__init__(*args, **kwargs)

    @declared_attr
    def _access_control_list(cls):  # pylint: disable=no-self-argument
        """access_control_list"""
        return db.relationship(
            'AccessControlList',
            primaryjoin=lambda: and_(
                remote(AccessControlList.object_id) == cls.id,
                remote(AccessControlList.object_type) == cls.__name__,
                remote(AccessControlList.parent_id_nn) == 0),
            foreign_keys='AccessControlList.object_id',
            backref='{0}_object'.format(cls.__name__),
            cascade='all, delete-orphan')

    @property
    def access_control_list(self):
        return [
            AclRecord(acp.person, acp.ac_list)
            for acl in self._access_control_list
            for acp in acl.access_control_people
        ]

    @cached_property
    def acr_acl_map(self):
        return {acl.ac_role: acl for acl in self._access_control_list}

    @cached_property
    def acr_name_acl_map(self):
        return {acl.ac_role.name: acl for acl in self._access_control_list}

    @cached_property
    def acr_id_acl_map(self):
        return {acl.ac_role.id: acl for acl in self._access_control_list}

    @access_control_list.setter
    def access_control_list(self, values):
        """Setter function for access control list.

    Args:
        values: List of access control roles or dicts containing json
        representation of custom attribute values.
    """
        if values is None:
            return

        new_acl_people_map = defaultdict(set)
        for value in values:
            if value["ac_role_id"] not in self.acr_id_acl_map:
                raise BadRequest(errors.BAD_PARAMS)
            person = referenced_objects.get("Person", value["person"]["id"])
            acl = self.acr_id_acl_map[value["ac_role_id"]]
            new_acl_people_map[acl].add(person)

        for acl in self._access_control_list:
            acl.update_people(new_acl_people_map[acl])

    @classmethod
    def eager_query(cls):
        """Eager Query"""
        query = super(Roleable, cls).eager_query()
        return query.options(
            orm.subqueryload('_access_control_list').joinedload(
                "ac_role").undefer_group('AccessControlRole_complete'),
            orm.subqueryload('_access_control_list').joinedload(
                "access_control_people").joinedload("person").undefer_group(
                    'Person_complete'),
        )

    @classmethod
    def indexed_query(cls):
        """Query used by the indexer"""
        query = super(Roleable, cls).indexed_query()
        return query.options(
            orm.subqueryload('_access_control_list').joinedload(
                "ac_role").load_only("id", "name", "object_type", "internal"),
            orm.subqueryload('_access_control_list').joinedload(
                "access_control_people").joinedload("person").load_only(
                    "id", "name", "email"),
        )

    @property
    def acl_json(self):
        """Get json representation of access_control_list.

    This function is a hack to preserve backwards compatibility with old
    revision logs.
    """
        acl_json = []
        for person, acl in self.access_control_list:
            person_entry = acl.log_json()
            person_entry["person"] = utils.create_stub(person)
            person_entry["person_email"] = person.email
            person_entry["person_id"] = person.id
            person_entry["person_name"] = person.name
            acl_json.append(person_entry)
        return acl_json

    def log_json(self):
        """Log custom attribute values."""
        # pylint: disable=not-an-iterable
        res = super(Roleable, self).log_json()
        res["access_control_list"] = self.acl_json
        return res

    def get_persons_for_rolename(self, role_name):
        """Return list of persons that are valid for send role_name."""
        return [
            acp.person
            for acp in self.acr_name_acl_map[role_name].access_control_people
        ]

    def get_person_ids_for_rolename(self, role_name):
        """Return list of persons that are valid for send role_name."""
        if role_name not in self.acr_name_acl_map:
            # This will be removed
            return []
        acps = self.acr_name_acl_map[role_name].access_control_people
        return [acp.person.id for acp in acps]

    def has_acl_changes(self):
        """Check if the object has had any acl changes in the session.

    Since access_control_list is now a normal property it no longer stores any
    history info that is needed for notifications. This helper function is
    meant to replace history check on access_control_list property.

    Returns:
      boolean flag signifying if there are any access control people changes
      in the current session.
    """
        return any(
            inspect(acl).attrs["access_control_people"].history.has_changes()
            for acl in self._access_control_list)

    def has_acr_acl_changed(self, acr_name):
        """Check if the object has had any changes in ACL with `acr_name` role.

    Helper function checking access control list with particular access
    control role `acr_name` for changes in current session. If there is no
    such role on object, `False` will be returned.

    Args:
      acr_name: Name of particular access control role to check for changes.

    Returns:
      Boolean indicating if there are any changes in particular access control
      list in the current session. If there is not any ACL with `acr_name`
      ACR, `False` will be returned.
    """
        if acr_name not in self.acr_name_acl_map:
            return False
        acl = self.acr_name_acl_map[acr_name]
        return inspect(
            acl).attrs["access_control_people"].history.has_changes()

    def validate_acl(self):
        """Check correctness of access_control_list."""
        for _, acl in self.access_control_list:
            if acl.object_type != acl.ac_role.object_type:
                raise ValueError(
                    "Access control list has different object_type '{}' with "
                    "access control role '{}'".format(acl.object_type,
                                                      acl.ac_role.object_type))
        self.validate_role_limit()

    def validate_role_limit(self, _import=False):
        """Validate the number of roles assigned to object

    Args:
      _import: if True than function return list of errors for 'add_error'
    """
        # This can now be fully refactored due to ACP, I am leaving this for the
        # end though.
        validation_errors = []
        count_roles = defaultdict(int)
        for _, acl in self.access_control_list:
            count_roles[acl.ac_role.name] += 1

        for _role in count_roles.keys():
            max_attr = "MAX_{}_NUM".format(_role).upper()
            _max = getattr(self, max_attr) if hasattr(self, max_attr) else None
            if _max and count_roles[_role] > _max:
                message = "{} role must have only {} person(s) assigned".format(
                    _role, _max)
                if _import:
                    validation_errors.append((_role, message))
                else:
                    raise ValueError(message)
        if _import:
            return validation_errors
        return None

    def add_person_with_role(self, person, ac_role):
        """Add a person to ACL object with a given role."""
        acl = self.acr_acl_map.get(ac_role)
        if not acl:
            logger.warning(
                "Trying to add invalid ac_role '%s' with id %s to %s(%s)",
                getattr(ac_role, "name", None),
                getattr(ac_role, "id", None),
                self.type,
                self.id,
            )
            return
        acl.add_person(person)

    def add_person_with_role_id(self, person, ac_role_id):
        """Add a person to ACL object with a given role id."""
        acl = self.acr_id_acl_map.get(ac_role_id)
        if not acl:
            logger.warning(
                "Trying to add invalid role by id %s to %s(%s)",
                ac_role_id,
                self.type,
                self.id,
            )
            return
        acl.add_person(person)

    def add_person_with_role_name(self, person, ac_role_name):
        """Add a person to ACL object with a given role name."""
        acl = self.acr_name_acl_map.get(ac_role_name)
        if not acl:
            logger.warning(
                "Trying to add invalid role by name %s to %s(%s)",
                ac_role_name,
                self.type,
                self.id,
            )
            return
        acl.add_person(person)
示例#2
0
class Roleable(object):
    """Roleable Mixin

  Mixin that adds access_control_list property to the parent object. Access
  control list includes a list of AccessControlList objects.
  """

    _update_raw = _include_links = _publish_attrs = ['access_control_list']
    _fulltext_attrs = [CustomRoleAttr('access_control_list')]

    @declared_attr
    def _access_control_list(self):
        """access_control_list"""
        return db.relationship(
            'AccessControlList',
            primaryjoin=lambda: and_(
                remote(AccessControlList.object_id) == self.id,
                remote(AccessControlList.object_type) == self.__name__),
            foreign_keys='AccessControlList.object_id',
            backref='{0}_object'.format(self.__name__),
            cascade='all, delete-orphan')

    @hybrid_property
    def access_control_list(self):
        return self._access_control_list

    @access_control_list.setter
    def access_control_list(self, values):
        """Setter function for access control list.

    Args:
      value: List of access control roles or dicts containing json
        representation of custom attribute values.
    """
        if values is None:
            return

        new_values = {(value['ac_role_id'], value['person']['id'])
                      for value in values}
        old_values = {(acl.ac_role_id, acl.person_id)
                      for acl in self.access_control_list}

        self._remove_values(old_values - new_values)
        self._add_values(new_values - old_values)

    def _add_values(self, values):
        """Attach new custom role values to current object."""
        for ac_role_id, person_id in values:
            AccessControlList(object=self,
                              person_id=person_id,
                              ac_role_id=ac_role_id)

    def _remove_values(self, values):
        """Remove custom role values from current object."""
        values_map = {(acl.ac_role_id, acl.person_id): acl
                      for acl in self.access_control_list}
        for value in values:
            self._access_control_list.remove(values_map[value])

    @classmethod
    def eager_query(cls):
        """Eager Query"""
        query = super(Roleable, cls).eager_query()
        return cls.eager_inclusions(query, Roleable._include_links).options(
            orm.subqueryload('access_control_list'))

    def log_json(self):
        """Log custom attribute values."""
        # pylint: disable=not-an-iterable
        res = super(Roleable, self).log_json()
        res["access_control_list"] = [
            value.log_json() for value in self.access_control_list
        ]
        return res
示例#3
0
class Roleable(object):
    """Roleable Mixin

  Mixin that adds access_control_list property to the parent object. Access
  control list includes a list of AccessControlList objects.
  """

    _update_raw = _include_links = [
        'access_control_list',
    ]
    _api_attrs = reflection.ApiAttributes(*_include_links)
    _fulltext_attrs = [
        CustomRoleAttr('access_control_list'),
    ]

    @declared_attr
    def _access_control_list(cls):  # pylint: disable=no-self-argument
        """access_control_list"""
        return db.relationship(
            'AccessControlList',
            primaryjoin=lambda: and_(
                remote(AccessControlList.object_id) == cls.id,
                remote(AccessControlList.object_type) == cls.__name__),
            foreign_keys='AccessControlList.object_id',
            backref='{0}_object'.format(cls.__name__),
            cascade='all, delete-orphan')

    @hybrid_property
    def access_control_list(self):
        return self._access_control_list

    @access_control_list.setter
    def access_control_list(self, values):
        """Setter function for access control list.

    Args:
      value: List of access control roles or dicts containing json
        representation of custom attribute values.
    """
        if values is None:
            return

        new_values = {
            (get("AccessControlRole",
                 value['ac_role_id']), get("Person", value['person']['id']))
            for value in values if value.get('ac_role_id') is not None
        } | {(value['ac_role'], value['person'])
             for value in values if value.get('ac_role') is not None
             and value.get('person') is not None}
        old_values = {(acl.ac_role, acl.person)
                      for acl in self.access_control_list}
        self._remove_values(old_values - new_values)
        self._add_values(new_values - old_values)

    def _add_values(self, values):
        """Attach new custom role values to current object."""
        for ac_role, person in values:
            AccessControlList(object=self, person=person, ac_role=ac_role)

    def _remove_values(self, values):
        """Remove custom role values from current object."""
        val_map = {(acl.ac_role, acl.person): acl
                   for acl in self.access_control_list}
        for value in values:
            self._access_control_list.remove(val_map[value])

    @classmethod
    def eager_query(cls):
        """Eager Query"""
        query = super(Roleable, cls).eager_query()
        return cls.eager_inclusions(query, Roleable._include_links).options(
            orm.subqueryload('access_control_list'))

    @classmethod
    def indexed_query(cls):
        query = super(Roleable, cls).indexed_query()
        return query.options(
            orm.subqueryload('access_control_list').subqueryload(
                "person").load_only(
                    "id",
                    "name",
                    "email",
                ))

    def log_json(self):
        """Log custom attribute values."""
        # pylint: disable=not-an-iterable
        res = super(Roleable, self).log_json()
        res["access_control_list"] = [
            value.log_json() for value in self.access_control_list
        ]
        return res

    def get_persons_for_rolename(self, role_name):
        """Return list of persons that are valid for send role_name."""
        for role_id, name in role.get_custom_roles_for(self.type).iteritems():
            if name != role_name:
                continue
            return [
                i.person for i in self.access_control_list
                if i.ac_role_id == role_id
            ]
        return []

    def get_person_ids_for_rolename(self, role_name):
        """Return list of persons that are valid for send role_name."""
        for role_id, name in role.get_custom_roles_for(self.type).iteritems():
            if name != role_name:
                continue
            return [
                i.person.id for i in self.access_control_list
                if i.ac_role.id == role_id
            ]
        return []
示例#4
0
class Roleable(object):
    """Roleable Mixin

  Mixin that adds access_control_list property to the parent object. Access
  control list includes a list of AccessControlList objects.
  """

    _update_raw = _include_links = [
        'access_control_list',
    ]
    _fulltext_attrs = [
        CustomRoleAttr('access_control_list'),
    ]
    _api_attrs = reflection.ApiAttributes(
        reflection.Attribute('access_control_list', True, True, True))
    MAX_ASSIGNEE_NUM = 1
    MAX_VERIFIER_NUM = 1

    @declared_attr
    def _access_control_list(cls):  # pylint: disable=no-self-argument
        """access_control_list"""
        return db.relationship(
            'AccessControlList',
            primaryjoin=lambda: and_(
                remote(AccessControlList.object_id) == cls.id,
                remote(AccessControlList.object_type) == cls.__name__,
                remote(AccessControlList.parent_id_nn) == 0),
            foreign_keys='AccessControlList.object_id',
            backref='{0}_object'.format(cls.__name__),
            cascade='all, delete-orphan')

    @hybrid_property
    def access_control_list(self):
        return self._access_control_list

    @access_control_list.setter
    def access_control_list(self, values):
        """Setter function for access control list.

    Args:
        values: List of access control roles or dicts containing json
        representation of custom attribute values.
    """
        if values is None:
            return

        new_values = self._parse_values(values)
        old_values = self._get_old_values()
        self._remove_values(old_values - new_values)
        self._add_values(new_values - old_values)

    def extend_access_control_list(self, values):
        """Extend access control list.

    Args:
        values: List of access control roles or dicts containing json
        representation of custom attribute values.
    """
        if values is None:
            return

        new_values = self._parse_values(values)
        old_values = self._get_old_values()
        self._add_values(new_values - old_values)

    def _get_old_values(self):
        """Return Set of tuples (Role, Person)"""
        return {(acl.ac_role, acl.person) for acl in self.access_control_list}

    @staticmethod
    def _parse_values(values):
        """ Parse list of (ac_role, user) in form of:
    e.g
    [{
        "ac_role_id": admin_role_id,
        "person": {
            "id": user_id
        }
    }]
    or
    [{
        "ac_role": AccessControlRole(),
        "person": Person()
    }]

    Return set of tuples(AccessControlRole, Person)"""

        result = set()
        for value in values:
            if ("ac_role_id" in value and "person" in value
                    and "id" in value["person"]):
                result.add((get("AccessControlRole", value["ac_role_id"]),
                            get("Person", value["person"]["id"])))
            elif "ac_role" in value and "person" in value:
                result.add((value["ac_role"], value["person"]))
            else:
                raise ValueError("Unknown values format")

        return result

    def _add_values(self, values):
        """Attach new custom role values to current object."""
        for ac_role, person in values:
            AccessControlList(object=self, person=person, ac_role=ac_role)

    def _remove_values(self, values):
        """Remove custom role values from current object."""
        val_map = {(acl.ac_role, acl.person): acl
                   for acl in self.access_control_list}
        for value in values:
            self._access_control_list.remove(val_map[value])

    @classmethod
    def eager_query(cls):
        """Eager Query"""
        query = super(Roleable, cls).eager_query()
        return cls.eager_inclusions(query, Roleable._include_links).options(
            orm.subqueryload('_access_control_list').joinedload(
                "person").undefer_group('Person_complete'),
            orm.subqueryload('_access_control_list').joinedload("person").
            subqueryload("contexts").undefer_group('Context_complete'),
            orm.subqueryload('_access_control_list').joinedload(
                "ac_role").undefer_group('AccessControlRole_complete'),
        )

    @classmethod
    def indexed_query(cls):
        """Query used by the indexer"""
        query = super(Roleable, cls).indexed_query()
        return query.options(
            orm.subqueryload("_access_control_list").load_only(
                "id",
                "person_id",
                "ac_role_id",
            ),
            orm.subqueryload('_access_control_list').joinedload(
                "person").load_only("id", "name", "email"),
            orm.subqueryload('_access_control_list').joinedload(
                "ac_role").load_only("id", "name", "object_type", "internal"),
        )

    def log_json(self):
        """Log custom attribute values."""
        # pylint: disable=not-an-iterable
        res = super(Roleable, self).log_json()
        res["access_control_list"] = [
            value.log_json() for value in self.access_control_list
        ]
        return res

    def get_persons_for_rolename(self, role_name):
        """Return list of persons that are valid for send role_name."""
        for role_id, name in role.get_custom_roles_for(self.type).iteritems():
            if name != role_name:
                continue
            return [
                i.person for i in self.access_control_list
                if i.ac_role_id == role_id
            ]
        return []

    def get_person_ids_for_rolename(self, role_name):
        """Return list of persons that are valid for send role_name."""
        for role_id, name in role.get_custom_roles_for(self.type).iteritems():
            if name != role_name:
                continue
            # TODO: use ac_role_id as temporary solution until GGRC-3784 implemented
            return [
                i.person.id for i in self.access_control_list
                if (i.ac_role and i.ac_role.id == role_id) or (
                    i.ac_role_id and i.ac_role_id == role_id)
            ]
        return []

    def validate_acl(self):
        """Check correctness of access_control_list."""
        for acl in self.access_control_list:
            if acl.object_type != acl.ac_role.object_type:
                raise ValueError(
                    "Access control list has different object_type '{}' with "
                    "access control role '{}'".format(acl.object_type,
                                                      acl.ac_role.object_type))
        self.validate_role_limit()

    def validate_role_limit(self, _import=False):
        """Validate the number of roles assigned to object

    Args:
      _import: if True than function return list of errors for 'add_error'
    """
        errors = []
        count_roles = defaultdict(int)
        for acl in self.access_control_list:
            count_roles[acl.ac_role.name] += 1

        for _role in count_roles.keys():
            max_attr = "MAX_{}_NUM".format(_role).upper()
            _max = getattr(self, max_attr) if hasattr(self, max_attr) else None
            if _max and count_roles[_role] > _max:
                message = "{} role must have only {} person(s) assigned".format(
                    _role, _max)
                if _import:
                    errors.append((_role, message))
                else:
                    raise ValueError(message)
        if _import:
            return errors