def permission_grants_for_user(self, name): # type: (str) -> List[PermissionGrant] now = datetime.utcnow() user = User.get(self.session, name=name) if not user or user.role_user or user.is_service_account or not user.enabled: return [] # Get the groups of which this user is a direct member. groups = ( self.session.query(Group.id) .join(GroupEdge, Group.id == GroupEdge.group_id) .join(User, User.id == GroupEdge.member_pk) .filter( Group.enabled == True, User.id == user.id, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["User"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ) .distinct() ) group_ids = [g.id for g in groups] # If the user was not a member of any group, we can return early. if not group_ids: return [] # Now, get the parent groups of those groups and so forth until we run out of levels of the # tree. Use a set of seen group_ids to avoid querying the same group twice if a user is a # member of it via multiple paths. seen_group_ids = set(group_ids) while group_ids: parent_groups = ( self.session.query(Group.id) .join(GroupEdge, Group.id == GroupEdge.group_id) .filter( GroupEdge.member_pk.in_(group_ids), Group.enabled == True, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["Group"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ) .distinct() ) group_ids = [g.id for g in parent_groups if g.id not in seen_group_ids] seen_group_ids.update(group_ids) # Return the permission grants. group_permission_grants = ( self.session.query(Permission.name, PermissionMap.argument) .filter( Permission.id == PermissionMap.permission_id, PermissionMap.group_id.in_(seen_group_ids), ) .all() ) return [PermissionGrant(g.name, g.argument) for g in group_permission_grants]
def test_group_edge_roles_order_unchanged(): # The order of the GROUP_EDGE_ROLES tuple matters: new roles must be # appended. This test attempts exposes that information to help prevent # that from happening accidentally. assert GROUP_EDGE_ROLES.index("member") == 0 assert GROUP_EDGE_ROLES.index("manager") == 1 assert GROUP_EDGE_ROLES.index("owner") == 2 assert GROUP_EDGE_ROLES.index("np-owner") == 3
def user_role_index(user, members): if user_is_group_admin(user.session, user): return GROUP_EDGE_ROLES.index("owner") member = members.get(("User", user.name)) if not member: return None return member.role
def permission_grants_for_user(self, name): # type: (str) -> List[GroupPermissionGrant] """Return all permission grants a user has from whatever source. TODO(rra): Currently does not expand permission aliases, and therefore doesn't match the graph behavior. Use with caution until that is fixed. """ now = datetime.utcnow() user = User.get(self.session, name=name) if not user or user.role_user or user.is_service_account or not user.enabled: return [] # Get the groups of which this user is a direct member. groups = (self.session.query(Group.id).join( GroupEdge, Group.id == GroupEdge.group_id).join( User, User.id == GroupEdge.member_pk).filter( Group.enabled == True, User.id == user.id, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["User"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ).distinct()) group_ids = [g.id for g in groups] # If the user was not a member of any group, we can return early. if not group_ids: return [] # Now, return all the permission grants for those groups. return self._permission_grants_for_group_ids(group_ids)
def _permission_grants_for_group_ids(self, group_ids): # type: (Iterable[int]) -> List[GroupPermissionGrant] """Given a set of group IDs, return all direct or inherited permission grants. Used to build the full list of permission grants for a set of groups, taking inheritance into account. Shared code between permission_grants_for_user and permission_grants_for_group. TODO(rra): Currently does not expand permission aliases, and therefore doesn't match the graph behavior. """ # Get the parent groups of the initial groups, repeating until we run out of levels of the # tree. Use a set of seen group_ids to avoid querying the same group twice if a user is a # member of it via multiple paths. now = datetime.utcnow() seen_group_ids = set(group_ids) while group_ids: parent_groups = (self.session.query(Group.id).join( GroupEdge, Group.id == GroupEdge.group_id).filter( GroupEdge.member_pk.in_(group_ids), Group.enabled == True, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["Group"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ).distinct()) group_ids = [ g.id for g in parent_groups if g.id not in seen_group_ids ] seen_group_ids.update(group_ids) # Return the permission grants. group_permission_grants = (self.session.query( Group.groupname, Permission.name, PermissionMap.argument, PermissionMap.granted_on, PermissionMap.id, ).filter( Permission.id == PermissionMap.permission_id, PermissionMap.group_id.in_(seen_group_ids), Group.id == PermissionMap.group_id, ).all()) return [ GroupPermissionGrant( group=g.groupname, permission=g.name, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in group_permission_grants ]
def _create_edge(session, group, member, role): # type: (Session, Group, Union[User, Group], str) -> GroupEdge edge, new = GroupEdge.get_or_create(session, group_id=group.id, member_type=member.member_type, member_pk=member.id) if new: # TODO(herb): this means all requests by this user to this group will # have the same role. we should probably record the role specifically # on the request and use that as the source on the UI edge._role = GROUP_EDGE_ROLES.index(role) session.flush() return edge
def add_group_to_group(self, member, group): # type: (str, str) -> None self.create_group(member) self.create_group(group) member_obj = Group.get(self.session, name=member) assert member_obj group_obj = Group.get(self.session, name=group) assert group_obj edge = GroupEdge( group_id=group_obj.id, member_type=OBJ_TYPES["Group"], member_pk=member_obj.id, active=True, _role=GROUP_EDGE_ROLES.index("member"), ) edge.add(self.session)
def add_user_to_group(self, user, group, role="member"): # type: (str, str, str) -> None self.create_user(user) self.create_group(group) user_obj = User.get(self.session, name=user) assert user_obj group_obj = Group.get(self.session, name=group) assert group_obj edge = GroupEdge( group_id=group_obj.id, member_type=OBJ_TYPES["User"], member_pk=user_obj.id, active=True, _role=GROUP_EDGE_ROLES.index(role), ) edge.add(self.session)
def add_user_to_group(self, user, group, role="member", expiration=None): # type: (str, str, str, Optional[datetime]) -> None self.create_user(user) self.create_group(group) user_obj = User.get(self.session, name=user) assert user_obj group_obj = Group.get(self.session, name=group) assert group_obj edge = GroupEdge( group_id=group_obj.id, member_type=OBJ_TYPES["User"], member_pk=user_obj.id, expiration=expiration, active=True, _role=GROUP_EDGE_ROLES.index(role), ) edge.add(self.session)
def groups_of_user(self, username): # type: (str) -> List[str] now = datetime.utcnow() user = User.get(self.session, name=username) if not user or user.role_user or user.is_service_account or not user.enabled: return [] groups = (self.session.query(Group.groupname).join( GroupEdge, Group.id == GroupEdge.group_id).join( User, User.id == GroupEdge.member_pk).filter( Group.enabled == True, User.id == user.id, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["User"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ).distinct()) return [g.groupname for g in groups]
def will_update_group_membership(self, session, group, member, **updates): if member.member_type != OBJ_TYPES["User"]: return check_permanent_owners = False if "role" in updates: role_idx = GROUP_EDGE_ROLES.index(updates["role"]) if role_idx not in OWNER_ROLE_INDICES: check_permanent_owners = True if updates.get("expiration"): check_permanent_owners = True if "active" in updates and not updates["active"]: check_permanent_owners = True if check_permanent_owners and _is_last_permanent_owner(session, group, member): raise PluginRejectedGroupMembershipUpdate(EXCEPTION_MESSAGE)
def role(self, role): # type: (str) -> None prev_role = self._role self._role = GROUP_EDGE_ROLES.index(role) # Groups should always "member". if not (OBJ_TYPES_IDX[self.member_type] == "User"): return # If ownership status is unchanged, no notices need to be adjusted. if (self._role in OWNER_ROLE_INDICES) == (prev_role in OWNER_ROLE_INDICES): return user = User.get(self.session, pk=self.member_pk) assert user recipient = user.username expiring_supergroups = self.group.my_expiring_groups() member_name = self.group.name if role in ["owner", "np-owner"]: # We're creating a new owner, who should find out when this group # they now own loses its membership in larger groups. for supergroup_name, expiration in expiring_supergroups: add_expiration( self.session, expiration, group_name=supergroup_name, member_name=member_name, recipients=[recipient], member_is_user=False, ) else: # We're removing an owner, who should no longer find out when this # group they no longer own loses its membership in larger groups. for supergroup_name, _ in expiring_supergroups: cancel_expiration( self.session, group_name=supergroup_name, member_name=member_name, recipients=[recipient], )
def will_update_group_membership(self, session, group, member, **updates): if member.member_type != OBJ_TYPES["User"]: return check_permanent_owners = False if "role" in updates: role_idx = GROUP_EDGE_ROLES.index(updates["role"]) if role_idx not in OWNER_ROLE_INDICES: check_permanent_owners = True if updates.get("expiration"): check_permanent_owners = True if "active" in updates and not updates["active"]: check_permanent_owners = True if check_permanent_owners and _is_last_permanent_owner( session, group, member): raise PluginRejectedGroupMembershipUpdate(EXCEPTION_MESSAGE)
def groups_of_user(self, username): # type: (str) -> List[str] now = datetime.utcnow() user = User.get(self.session, name=username) if not user or user.role_user or user.is_service_account or not user.enabled: return [] groups = ( self.session.query(Group.groupname) .join(GroupEdge, Group.id == GroupEdge.group_id) .join(User, User.id == GroupEdge.member_pk) .filter( Group.enabled == True, User.id == user.id, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["User"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ) .distinct() ) return [g.groupname for g in groups]
def permission_grants_for_user(self, name): # type: (str) -> List[GroupPermissionGrant] """Return all permission grants a user has from whatever source. TODO(rra): Currently does not expand permission aliases, and therefore doesn't match the graph behavior. Use with caution until that is fixed. """ now = datetime.utcnow() user = User.get(self.session, name=name) if not user or user.role_user or user.is_service_account or not user.enabled: return [] # Get the groups of which this user is a direct member. groups = ( self.session.query(Group.id) .join(GroupEdge, Group.id == GroupEdge.group_id) .join(User, User.id == GroupEdge.member_pk) .filter( Group.enabled == True, User.id == user.id, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["User"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ) .distinct() ) group_ids = [g.id for g in groups] # If the user was not a member of any group, we can return early. if not group_ids: return [] # Now, get the parent groups of those groups and so forth until we run out of levels of the # tree. Use a set of seen group_ids to avoid querying the same group twice if a user is a # member of it via multiple paths. seen_group_ids = set(group_ids) while group_ids: parent_groups = ( self.session.query(Group.id) .join(GroupEdge, Group.id == GroupEdge.group_id) .filter( GroupEdge.member_pk.in_(group_ids), Group.enabled == True, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["Group"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ) .distinct() ) group_ids = [g.id for g in parent_groups if g.id not in seen_group_ids] seen_group_ids.update(group_ids) # Return the permission grants. group_permission_grants = ( self.session.query( Group.groupname, Permission.name, PermissionMap.argument, PermissionMap.granted_on, PermissionMap.id, ) .filter( Permission.id == PermissionMap.permission_id, PermissionMap.group_id.in_(seen_group_ids), Group.id == PermissionMap.group_id, ) .all() ) return [ GroupPermissionGrant( group=g.groupname, permission=g.name, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in group_permission_grants ]
def permission_grants_for_user(self, name): # type: (str) -> List[GroupPermissionGrant] """Return all permission grants a user has from whatever source. TODO(rra): Currently does not expand permission aliases, and therefore doesn't match the graph behavior. Use with caution until that is fixed. """ now = datetime.utcnow() user = User.get(self.session, name=name) if not user or user.role_user or user.is_service_account or not user.enabled: return [] # Get the groups of which this user is a direct member. groups = (self.session.query(Group.id).join( GroupEdge, Group.id == GroupEdge.group_id).join( User, User.id == GroupEdge.member_pk).filter( Group.enabled == True, User.id == user.id, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["User"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ).distinct()) group_ids = [g.id for g in groups] # If the user was not a member of any group, we can return early. if not group_ids: return [] # Now, get the parent groups of those groups and so forth until we run out of levels of the # tree. Use a set of seen group_ids to avoid querying the same group twice if a user is a # member of it via multiple paths. seen_group_ids = set(group_ids) while group_ids: parent_groups = (self.session.query(Group.id).join( GroupEdge, Group.id == GroupEdge.group_id).filter( GroupEdge.member_pk.in_(group_ids), Group.enabled == True, GroupEdge.active == True, GroupEdge.member_type == OBJ_TYPES["Group"], GroupEdge._role != GROUP_EDGE_ROLES.index("np-owner"), or_(GroupEdge.expiration > now, GroupEdge.expiration == None), ).distinct()) group_ids = [ g.id for g in parent_groups if g.id not in seen_group_ids ] seen_group_ids.update(group_ids) # Return the permission grants. group_permission_grants = (self.session.query( Group.groupname, Permission.name, PermissionMap.argument, PermissionMap.granted_on, PermissionMap.id, ).filter( Permission.id == PermissionMap.permission_id, PermissionMap.group_id.in_(seen_group_ids), Group.id == PermissionMap.group_id, ).all()) return [ GroupPermissionGrant( group=g.groupname, permission=g.name, argument=g.argument, granted_on=g.granted_on, is_alias=False, grant_id=g.id, ) for g in group_permission_grants ]