def get_audited_groups(session): # type: (Session) -> List[Group] """Returns all audited enabled groups. At present, this is not cached at all and returns the full list of groups from the database each time it's called. Args: session (Session): Session to load data on. Returns: a list of all enabled and audited Group objects in the database """ audited_groups = [] graph = Graph() for group in get_all_groups(session): try: group_md = graph.get_group_details(group.name) except NoSuchGroup: # Very new group with no metadata yet, or it has been disabled and # excluded from in-memory cache. continue if group_md.get('audited', False): audited_groups.append(group) return audited_groups
def promote_nonauditors(self, session): # type: (Session) -> None """Checks all enabled audited groups and ensures that all approvers for that group have the PERMISSION_AUDITOR permission. All non-auditor approvers of audited groups will be promoted to be auditors, i.e., added to the auditors group. Args: session (Session): database session """ graph = Graph() # Hack to ensure the graph is loaded before we access it graph.update_from_db(session) # map from user object to names of audited groups in which # user is a nonauditor approver nonauditor_approver_to_groups = defaultdict( set) # type: Dict[User, Set[str]] user_is_auditor = {} # type: Dict[str, bool] for group_tuple in graph.get_groups(audited=True, directly_audited=False): group_md = graph.get_group_details(group_tuple.groupname, expose_aliases=False) for username, user_md in iteritems(group_md["users"]): if username not in user_is_auditor: user_perms = graph.get_user_details( username)["permissions"] user_is_auditor[username] = any([ p["permission"] == PERMISSION_AUDITOR for p in user_perms ]) if user_is_auditor[username]: # user is already auditor so can skip continue if user_md["role"] in APPROVER_ROLE_INDICES: # non-auditor approver. BAD! nonauditor_approver_to_groups[username].add( group_tuple.groupname) if nonauditor_approver_to_groups: auditors_group = get_auditors_group(self.settings, session) for username, group_names in iteritems( nonauditor_approver_to_groups): reason = "auto-added due to having approver role(s) in group(s): {}".format( ", ".join(group_names)) user = User.get(session, name=username) assert user auditors_group.add_member(user, user, reason, status="actioned") notify_nonauditor_promoted(self.settings, session, user, auditors_group, group_names) session.commit()
def assert_controllers_are_auditors(group): # type: (Group) -> bool """Return whether not all owners/np-owners/managers in a group (and below) are auditors This is used to ensure that all of the people who can control a group (owners, np-owners, managers) and all subgroups (all the way down the tree) have audit permissions. Raises: UserNotAuditor: If a user is found that violates the audit training policy, then this exception is raised. Returns: bool: True if the tree is completely controlled by auditors, else it will raise as above. """ graph = Graph() checked = set() # type: Set[str] queue = [group.name] while queue: cur_group = queue.pop() if cur_group in checked: continue details = graph.get_group_details(cur_group) for chk_user, info in iteritems(details["users"]): if chk_user in checked: continue # Only examine direct members of this group, because then the role is accurate. if info["distance"] == 1: if info["rolename"] == "member": continue if user_is_auditor(chk_user): checked.add(chk_user) else: raise UserNotAuditor( "User {} has role '{}' in the group {} but lacks the auditing " "permission ('{}').".format( chk_user, info["rolename"], cur_group, PERMISSION_AUDITOR ) ) # Now put subgroups into the queue to examine. for chk_group, info in iteritems(details["subgroups"]): if info["distance"] == 1: queue.append(chk_group) # If we didn't raise, we're valid. return True
def assert_can_join(group, user_or_group, role="member"): # type: (Group, Union[Group, User], str) -> bool """Enforce audit rules on joining a group This applies the auditing rules to determine whether or not a given user can join the given group with the given role. Args: group (models.Group): The group to test against. user (models.User): The user attempting to join. role (str): The role being tested. Raises: UserNotAuditor: If a user is found that violates the audit training policy, then this exception is raised. Returns: bool: True if the user should be allowed per policy, else it will raise as above. """ # By definition, any user can join as a member to any group. if user_or_group.type == "User" and role == "member": return True # Else, we have to check if the group is audited. If not, anybody can join. graph = Graph() group_md = graph.get_group_details(group.name) if not group_md["audited"]: return True # Audited group. Easy case, let's see if we're checking a user. If so, the user must be # considered an auditor. if user_or_group.type == "User": if user_is_auditor(user_or_group.name): return True raise UserNotAuditor( "User {} lacks the auditing permission ('{}') so may only have the " "'member' role in this audited group.".format(user_or_group.name, PERMISSION_AUDITOR) ) # No, this is a group-joining-group case. In this situation we must walk the entire group # subtree and ensure that all owners/np-owners/managers are considered auditors. This data # is contained in the group metadetails, which contains all eventual members. # # We have to fetch each group's details individually though to figure out what someone's role # is in that particular group. return assert_controllers_are_auditors(user_or_group)
def promote_nonauditors(self, session): # type: (Session) -> None """Checks all enabled audited groups and ensures that all approvers for that group have the PERMISSION_AUDITOR permission. All non-auditor approvers of audited groups will be promoted to be auditors, i.e., added to the auditors group. Args: session (Session): database session """ graph = Graph() # Hack to ensure the graph is loaded before we access it graph.update_from_db(session) # map from user object to names of audited groups in which # user is a nonauditor approver nonauditor_approver_to_groups = defaultdict(set) # type: Dict[User, Set[str]] user_is_auditor = {} # type: Dict[str, bool] for group_tuple in graph.get_groups(audited=True, directly_audited=False): group_md = graph.get_group_details(group_tuple.name, expose_aliases=False) for username, user_md in iteritems(group_md["users"]): if username not in user_is_auditor: user_perms = graph.get_user_details(username)["permissions"] user_is_auditor[username] = any( [p["permission"] == PERMISSION_AUDITOR for p in user_perms] ) if user_is_auditor[username]: # user is already auditor so can skip continue if user_md["role"] in APPROVER_ROLE_INDICES: # non-auditor approver. BAD! nonauditor_approver_to_groups[username].add(group_tuple.name) if nonauditor_approver_to_groups: auditors_group = get_auditors_group(self.settings, session) for username, group_names in iteritems(nonauditor_approver_to_groups): reason = "auto-added due to having approver role(s) in group(s): {}".format( ", ".join(group_names) ) user = User.get(session, name=username) assert user auditors_group.add_member(user, user, reason, status="actioned") notify_nonauditor_promoted( self.settings, session, user, auditors_group, group_names ) session.commit()
def assert_controllers_are_auditors(group: Group) -> None: """Return whether not all owners/np-owners/managers in a group (and below) are auditors This is used to ensure that all of the people who can control a group (owners, np-owners, managers) and all subgroups (all the way down the tree) have audit permissions. Raises: UserNotAuditor: If a user is found that violates the audit training policy """ graph = Graph() checked: Set[str] = set() queue = [group.name] while queue: cur_group = queue.pop() if cur_group in checked: continue checked.add(cur_group) details = graph.get_group_details(cur_group) for chk_user, info in details["users"].items(): if chk_user in checked: continue # Only examine direct members of this group, because then the role is accurate. if info["distance"] == 1: if info["rolename"] == "member": continue if user_is_auditor(chk_user): checked.add(chk_user) else: raise UserNotAuditor( "User {} has role '{}' in the group {} but lacks the auditing " "permission ('{}').".format( chk_user, info["rolename"], cur_group, PERMISSION_AUDITOR ) ) # Now put subgroups into the queue to examine. for chk_group, info in details["subgroups"].items(): if info["distance"] == 1: queue.append(chk_group)