Beispiel #1
0
    def edit_helper(self, param_list, user_id) -> ResponseTuple:
        """
        Edit the properties of a specific team.

        Team leads can only edit the teams that they are a part of, but admins
        can edit any teams.

        :param param_list: List of parameters for editing team
        :param user_id: Slack ID of user who called command
        :return: error message if user has insufficient permission level or
                 team edited unsuccessfully, otherwise return success message
        """
        try:
            command_user = self.facade.retrieve(User, user_id)
            command_team = param_list['team_name']
            team = get_team_by_name(self.facade, command_team)
            if not check_permissions(command_user, team):
                return self.permission_error, 200
            msg = f"Team edited: {command_team}, "
            if param_list['name'] is not None:
                msg += f"name: {param_list['name']}, "
                team.display_name = param_list['name']
            if param_list['platform'] is not None:
                msg += f"platform: {param_list['platform']}"
                team.platform = param_list['platform']
            if param_list['folder'] is not None:
                msg += f"folder: {param_list['folder']}"
                team.folder = param_list['folder']
            self.facade.store(team)
            ret = {'attachments': [team.get_attachment()], 'text': msg}
            return ret, 200
        except LookupError:
            return self.lookup_error, 200
Beispiel #2
0
    def handle_added(self,
                     github_id: str,
                     github_username: str,
                     organization: str) -> ResponseTuple:
        """Help organization function if payload action is added."""
        logging.info(f"user {github_username} added to {organization}")
        all_name = self._conf.github_team_all

        try:
            # Try to add the user to the 'all' team if it exists
            team_all = get_team_by_name(self._facade, all_name)
            self._gh.add_team_member(github_username, team_all.github_team_id)
            team_all.add_member(github_id)
            self._facade.store(team_all)
        except LookupError:
            # If that team doesn't exist, make it exist
            t_id = str(self._gh.org_create_team(self._conf.github_team_all))
            self._gh.add_team_member(github_username, t_id)
            logging.info(f'team {all_name} created for {organization}')
            team = Team(t_id, all_name, all_name)
            team.add_member(github_id)
            self._facade.store(team)
        except RuntimeError as e:
            # If there are any other kinds of errors, we log it
            logging.error(e)
            return '', 200

        logging.info(f'user {github_username} added to team {all_name}')
        return f"user {github_username} added to {organization}", 200
Beispiel #3
0
    def view_helper(self, team_name) -> ResponseTuple:
        """
        Return display information and members of specified team.

        :param team_name: name of team being viewed
        :return: error message if team not found,
                 otherwise return team information
        """
        try:
            team = get_team_by_name(self.facade, team_name)
            team_leads_set = team.team_leads
            team_leads_list = list(
                map(lambda i: ('github_user_id', str(i)), team_leads_set))
            team_leads: List[User] = []
            if team_leads_list:
                team_leads = self.facade.query_or(User, team_leads_list)
            names = set(map(lambda m: m.github_username, team_leads))
            team.team_leads = names

            members_set = team.members
            members_list = list(
                map(lambda i: ('github_user_id', str(i)), members_set))
            members: List[User] = []
            if members_list:
                members = self.facade.query_or(User, members_list)
            names = set(map(lambda m: m.github_username, members))
            team.members = names
            return {'attachments': [team.get_attachment()]}, 200
        except LookupError:
            return self.lookup_error, 200
Beispiel #4
0
    def team_delete(self, caller_id: str, gh_team_name: str) -> None:
        """
        Permanently delete a team.

        :param gh_team_name: Github team name of the team to delete
        :param caller_id: Slack ID of user who called command
        :raises: LookupError if the calling user or the team to delete could
                 not be found
        :raises: RuntimeError if more than one team has the specified
                 team name
        :raises: PermissionError if the calling user does not have sufficient
                 permissions to delete the specified team
        """
        logging.info("Team delete command API called")
        command_user = self._db_facade.retrieve(User, caller_id)
        logging.debug(f"Calling user: {command_user.__str__()}")

        team = db_utils.get_team_by_name(self._db_facade, gh_team_name)

        if not check_permissions(command_user, team):
            msg = f"User with ID {caller_id} has insufficient permissions" \
                f" to delete team {gh_team_name}"
            logging.error(msg)
            raise PermissionError(msg)

        self._gh_interface.org_delete_team(int(team.github_team_id))

        self._db_facade.delete(Team, team.github_team_id)
        logging.info(f"{gh_team_name} successfully deleted")
Beispiel #5
0
    def team_lead(self,
                  caller_id: str,
                  lead_id: str,
                  gh_team_name: str,
                  remove: bool = False) -> bool:
        """
        Add a user as a team lead, and add them to team if not already added.

        :param caller_id: Slack ID of user who called command
        :param lead_id: Slack ID of user to declare as team lead
        :param gh_team_name: Github team name of team to add a lead to
        :param remove: if True, removes the user as team lead of the team
        :raises: LookupError if the calling user, the team to add a lead to
                 could not be found, the user is not on the team, or the user
                 is not a lead on the team
        :raises: RuntimeError if more than one team has the specified
                 team name
        :raises: PermissionError if the calling user does not have sufficient
                 permissions to add a lead to the specified team
        :returns: True if removal was successful, False otherwise
        """
        logging.info("Team lead command API called")
        command_user = self._db_facade.retrieve(User, caller_id)
        logging.debug(f"Calling user: {command_user.__str__()}")

        team = db_utils.get_team_by_name(self._db_facade, gh_team_name)

        if not check_permissions(command_user, team):
            msg = f"User with ID {caller_id} has insufficient permissions" \
                f" to add lead to team {gh_team_name}"
            logging.error(msg)
            raise PermissionError(msg)

        lead_user = self._db_facade.retrieve(User, lead_id)
        logging.debug(f"User to add as lead: {lead_user.__str__()}")

        if remove:
            if not team.has_member(lead_user.github_id):
                msg = f"User with Github ID {lead_user.github_id} not a " \
                    "member of specified team"
                logging.error(msg)
                raise LookupError(msg)
            if team.has_team_lead(lead_user.github_id):
                team.discard_team_lead(lead_user.github_id)
                discarded = self._db_facade.store(team)
                return discarded
            else:
                msg = f"User with Github ID {lead_user.github_id} not a " \
                    "lead of specified team"
                logging.error(msg)
                raise LookupError(msg)
        else:
            if not team.has_member(lead_user.github_id):
                team.add_member(lead_user.github_id)
                self._gh_interface.add_team_member(lead_user.github_username,
                                                   team.github_team_id)
            team.add_team_lead(lead_user.github_id)
            added = self._db_facade.store(team)
            return added
Beispiel #6
0
    def add_helper(self, args: Namespace, user_id: str) -> ResponseTuple:
        """
        Add user to team.

        If user is not admin or team lead of specified team, the user will not
        be added and an error message is returned.

        :param args: Parameters for adding user
        :param user_id: Slack ID of user who called command
        :return: error message if user added unsuccessfully or if user has
                 insufficient permission level, otherwise returns success
                 message
        """
        try:
            command_user = self.facade.retrieve(User, user_id)
            command_team = args.team_name
            team = get_team_by_name(self.facade, command_team)
            if not check_permissions(command_user, team):
                return self.permission_error, 200

            user = self.facade.retrieve(User, args.username)
            if len(user.github_id) == 0:
                return self.no_ghusername_error, 200
            team.add_member(user.github_id)
            self.gh.add_team_member(user.github_username, team.github_team_id)
            self.facade.store(team)
            msg = "Added User to " + command_team

            # Update drive shares
            sync_team_email_perms(self.gcp, self.facade, team)

            # If this team is a team with special permissions, promote the
            # user to the appropriate permission
            promoted_level = Permissions.member
            if command_team == self.config.github_team_admin:
                promoted_level = Permissions.admin
            elif command_team == self.config.github_team_leads:
                promoted_level = Permissions.team_lead

            # Only perform promotion if it is actually a promotion.
            if promoted_level > user.permissions_level:
                logging.info(f"Promoting {command_user} to {promoted_level}")
                user.permissions_level = promoted_level
                self.facade.store(user)
                msg += f" and promoted user to {promoted_level}"
            ret = {'attachments': [team.get_attachment()], 'text': msg}
            return ret, 200

        except LookupError:
            return self.lookup_error, 200
        except GithubAPIException as e:
            logging.error("user added unsuccessfully to team")
            return f"User added unsuccessfully with the " \
                   f"following error: {e.data}", 200
Beispiel #7
0
    def team_view(self, gh_team_name: str) -> Team:
        """
        View team information from the database.

        :param gh_team_name: name of the team to view
        :raises: LookupError if a team with the specified team name
                 does not exist
        :raises: RuntimeError if more than one team has the specified
                 team name
        :return: ``Team`` object if found
        """
        logging.info("Team view command API called")
        return db_utils.get_team_by_name(self._db_facade, gh_team_name)
Beispiel #8
0
    def team_remove(self, caller_id: str, gh_team_name: str,
                    rem_user_id: str) -> bool:
        """
        Remove the specified user from a team.

        If the user is also a team lead, removes team lead status from Team.

        :param caller_id: Slack ID of user who called command
        :param gh_team_name: Github team name of the team to remove user from
        :param rem_user_id: Slack ID of user to remove from team
        :raises: LookupError if the calling user, user to remove,
                 or specified team cannot be found in the database
        :raises: RuntimeError if more than one team has the specified
                 team name
        :raises: PermissionError if the calling user has insufficient
                 permission to remove members to the specified team
        :raises: GithubAPIException if an error occured removing the user from
                 the Github team
        :return: True if user was removed from team successfully,
                 False otherwise
        """
        logging.info("Team remove command API called")
        command_user = self._db_facade.retrieve(User, caller_id)
        logging.debug(f"Calling user: {command_user.__str__()}")

        team = db_utils.get_team_by_name(self._db_facade, gh_team_name)

        if not check_permissions(command_user, team):
            msg = f"User with ID {caller_id} has insufficient permissions" \
                f" to remove members to team {gh_team_name}"
            logging.error(msg)
            raise PermissionError(msg)

        rem_user = self._db_facade.retrieve(User, rem_user_id)
        logging.debug(f"User to remove: {rem_user.__str__()}")

        if not self._gh_interface.has_team_member(rem_user.github_username,
                                                  team.github_team_id):
            msg = f"Github user {rem_user.github_username} not a member" \
                f" of Github team with ID {team.github_team_id}"
            logging.error(msg)
            raise GithubAPIException(msg)
        self._gh_interface.remove_team_member(rem_user.github_username,
                                              team.github_team_id)
        team.discard_member(rem_user.github_id)
        if team.has_team_lead(rem_user.github_id):
            team.discard_team_lead(rem_user.github_id)

        removed = self._db_facade.store(team)
        return removed
Beispiel #9
0
    def lead_helper(self, param_list, user_id) -> ResponseTuple:
        """
        Add a user as team lead, and add them to team if not already added.

        If ``--remove`` flag is used, user is instead demoted from being a team
        lead, but not from the team.

        :param param_list: List of parameters for editing leads
        :param user_id: Slack ID of user who called command
        :return: error message if user has insufficient permission level or
                 lead demoted unsuccessfully, otherwise return success message
        """
        try:
            command_user = self.facade.retrieve(User, user_id)
            command_team = param_list['team_name']
            team = get_team_by_name(self.facade, command_team)
            if not check_permissions(command_user, team):
                return self.permission_error, 200
            user = self.facade.retrieve(User, param_list["username"])
            msg = ""
            if param_list["remove"]:
                if not team.has_member(user.github_id):
                    return "User not in team!", 200
                if team.has_team_lead(user.github_id):
                    team.discard_team_lead(user.github_id)
                self.facade.store(team)
                msg = f"User removed as team lead from" \
                      f" {command_team}"
            else:
                if not team.has_member(user.github_id):
                    team.add_member(user.github_id)
                    self.gh.add_team_member(user.github_username,
                                            team.github_team_id)
                team.add_team_lead(user.github_id)
                self.facade.store(team)
                msg = f"User added as team lead to" \
                      f" {command_team}"
            ret = {'attachments': [team.get_attachment()], 'text': msg}
            return ret, 200
        except LookupError:
            return self.lookup_error, 200
        except GithubAPIException as e:
            logging.error("team lead edit unsuccessful")
            return f"Edit team lead was unsuccessful " \
                   f"with the following error: {e.data}", 200
Beispiel #10
0
    def team_edit(self,
                  caller_id: str,
                  gh_team_name: str,
                  display_name: str = None,
                  platform: str = None) -> bool:
        """
        Edit the properties of a specific team.

        Team leads can only edit the teams that they are a part of, but admins
        can edit any team.

        :param caller_id: Slack ID of user who called command
        :param display_name: display name to change to if not None
        :param platform: platform to change to if not None
        :raises: LookupError if the calling user or team to edit
                 could not be found
        :raises: RuntimeError if more than one team has the specified
                 team name
        :raises: PermissionError if the calling user does not have sufficient
                 permissions to edit the specified team
        :return: True if the edit was successful, False otherwise
        """
        logging.info("Team edit command API called")
        command_user = self._db_facade.retrieve(User, caller_id)
        logging.debug(f"Calling user: {command_user.__str__()}")

        team = db_utils.get_team_by_name(self._db_facade, gh_team_name)

        if not check_permissions(command_user, team):
            msg = f"User with ID {caller_id} has insufficient permissions" \
                f" to edit team {gh_team_name}"
            logging.error(msg)
            raise PermissionError(msg)

        if display_name is not None:
            logging.debug(f"Attaching display name {display_name} "
                          f"to {gh_team_name}")
            team.display_name = display_name

        if platform is not None:
            logging.debug(f"Attaching platform {platform} to {gh_team_name}")
            team.platform = platform

        edited = self._db_facade.store(team)
        return edited
Beispiel #11
0
    def refresh_all_rocket_permissions(self):
        """
        Refresh Rocket permissions for members in teams like
        GITHUB_ADMIN_TEAM_NAME and GITHUB_LEADS_TEAM_NAME.

        It only ever promotes users, and does not demote users.
        """
        # provide teams from low permissions level to high
        teams = [
            {
                'name': self.config.github_team_leads,
                'permission': Permissions.team_lead,
            },
            {
                'name': self.config.github_team_admin,
                'permission': Permissions.admin,
            },
        ]
        logging.info(f'refreshing Rocket permissions for teams {teams}')
        for t in teams:
            team_name = t['name']
            if len(team_name) == 0:
                continue

            team = None
            try:
                team = get_team_by_name(self.facade, team_name)
            except LookupError:
                t_id = str(self.gh.org_create_team(team_name))
                logging.info(f'team {team_name} created')
                self.facade.store(Team(t_id, team_name, team_name))

            if team is not None:
                team_members = get_team_members(self.facade, team)
                updated = []
                for user in team_members:
                    if user.permissions_level < t['permission']:
                        user.permissions_level = t['permission']
                        updated.append(user)
                        self.facade.store(user)
                if len(updated) > 0:
                    logging.info(f'updated users {updated}')
                else:
                    logging.info('no users updated')
Beispiel #12
0
    def edit_helper(self, args: Namespace, user_id: str) -> ResponseTuple:
        """
        Edit the properties of a specific team.

        Team leads can only edit the teams that they are a part of, but admins
        can edit any teams.

        :param args: Parameters for editing team
        :param user_id: Slack ID of user who called command
        :return: error message if user has insufficient permission level or
                 team edited unsuccessfully, otherwise return success message
        """
        try:
            command_user = self.facade.retrieve(User, user_id)
            command_team = args.team_name
            team = get_team_by_name(self.facade, command_team)
            if not check_permissions(command_user, team):
                return self.permission_error, 200
            msg = f"Team edited: {command_team}, "
            if args.displayname is not None:
                msg += f"displayname: {args.displayname}, "
                team.displayname = args.displayname
            if args.platform is not None:
                msg += f"platform: {args.platform}"
                team.platform = args.platform
            if args.folder is not None:
                msg += f"folder: {args.folder}"
                team.folder = args.folder
            if args.github is not None:
                msg += f"new github team name: {args.github}"
                team.github_team_name = args.github
                self.gh.org_edit_team(
                    int(team.github_team_id), team.github_team_name)
            self.facade.store(team)

            # Update drive shares if folder was changed
            if args.folder:
                sync_team_email_perms(self.gcp, self.facade, team)

            ret = {'attachments': [team.get_attachment()], 'text': msg}
            return ret, 200
        except LookupError:
            return self.lookup_error, 200
Beispiel #13
0
    def team_add(self, caller_id: str, add_user_id: str,
                 gh_team_name: str) -> bool:
        """
        Add a user to a team.

        :param caller_id: Slack ID of user who called the API
        :param add_user_id: Slack ID of user to add to a team
        :param gh_team_name: Github team name of the team to add a user to
        :raises: LookupError if the calling user, user to add,
                 or specified team cannot be found in the database
        :raises: RuntimeError if more than one team has the specified
                 team name
        :raises: PermissionError if the calling user has insufficient
                 permission to add members to the specified team
        :raises: GithubAPIException if an error occurs when adding the user to
                 the Github team
        :return: True if adding the member to the team was successful,
                 False otherwise
        """
        logging.info("Team add command API called")
        command_user = self._db_facade.retrieve(User, caller_id)
        logging.debug(f"Calling user: {command_user.__str__()}")

        team = db_utils.get_team_by_name(self._db_facade, gh_team_name)

        if not check_permissions(command_user, team):
            msg = f"User with ID {caller_id} has insufficient permissions" \
                f" to add members to team {gh_team_name}"
            logging.error(msg)
            raise PermissionError(msg)

        add_user = self._db_facade.retrieve(User, add_user_id)
        logging.debug(f"User to add: {add_user.__str__()}")

        self._gh_interface.add_team_member(add_user.github_username,
                                           team.github_team_id)
        team.add_member(add_user.github_id)

        added = self._db_facade.store(team)
        return added
Beispiel #14
0
    def delete_helper(self, team_name, user_id) -> ResponseTuple:
        """
        Permanently delete a team.

        :param team_name: Name of team to be deleted
        :param user_id: Slack ID of user who called command
        :return: error message if user has insufficient permission level or
                 team deleted unsuccessfully, otherwise return success message
        """
        try:
            command_user = self.facade.retrieve(User, user_id)
            team = get_team_by_name(self.facade, team_name)
            if not check_permissions(command_user, team):
                return self.permission_error, 200
            self.facade.delete(Team, team.github_team_id)
            self.gh.org_delete_team(int(team.github_team_id))
            return f"Team {team_name} deleted", 200
        except LookupError:
            return self.lookup_error, 200
        except GithubAPIException as e:
            logging.error("team delete unsuccessful")
            return f"Team delete was unsuccessful with " \
                   f"the following error: {e.data}", 200
Beispiel #15
0
    def refresh_all_team(self):
        """
        Refresh the 'all' team - this team is used to track all members.

        Should only be called after the teams have all synced, or bugs will
        probably occur. See https://github.com/orgs/ubclaunchpad/teams/all
        """
        all_name = self.config.github_team_all
        team_all = None
        if len(all_name) == 0:
            logging.info('no "all" team configured, skipping refresh')
            return

        logging.info(f'refreshing all team {all_name}')
        try:
            team_all = get_team_by_name(self.facade, all_name)
        except LookupError:
            t_id = str(self.gh.org_create_team(all_name))
            logging.info(f'team {all_name} created')
            team_all = Team(t_id, all_name, all_name)

        if team_all is not None:
            all_members = self.facade.query(User)
            for m in all_members:
                if len(m.github_id) > 0 and\
                        not team_all.has_member(m.github_id):
                    # The only way for this to be true is if both locally and
                    # remotely the member (who is part of launchpad) is not
                    # part of the 'all' team.
                    self.gh.add_team_member(m.github_username,
                                            team_all.github_team_id)
                    team_all.add_member(m.github_id)

            self.facade.store(team_all)
        else:
            logging.error(f'Could not create {all_name}. Aborting.')
Beispiel #16
0
 def get_team_users(self, team_name):
     team = get_team_by_name(self.facade, team_name)
     return get_team_members(self.facade, team)
Beispiel #17
0
 def test_get_team_by_name_lots_of_teams_same_name(self):
     db = MemoryDB(teams=[self.t0, self.t1])
     with self.assertRaises(RuntimeError):
         get_team_by_name(db, 'some-team')
Beispiel #18
0
 def test_get_team_by_name_no_team_name(self):
     with self.assertRaises(LookupError):
         get_team_by_name(self.db, 'random-team')
Beispiel #19
0
    def remove_helper(self, param_list, user_id) -> ResponseTuple:
        """
        Remove specified user from a team.

        If the user is also a team lead, removes team lead status from Team. If
        user is not admin or team lead of specified team, user will not be
        removed and an error message is returned.

        :param param_list: List of parameters for removing user
        :param user_id: Slack ID of user who called command
        :return: error message if user removed unsuccessfully, if user is not
                 in team, or if user has insufficient permission level,
                 otherwise returns success message
        """
        try:
            command_user = self.facade.retrieve(User, user_id)
            command_team = param_list['team_name']
            team = get_team_by_name(self.facade, command_team)
            if not check_permissions(command_user, team):
                return self.permission_error, 200

            user = self.facade.retrieve(User, param_list['username'])
            if not self.gh.has_team_member(user.github_username,
                                           team.github_team_id):
                return "User not in team!", 200
            team.discard_member(user.github_id)
            if team.has_team_lead(user.github_id):
                team.discard_team_lead(user.github_id)
            self.gh.remove_team_member(user.github_username,
                                       team.github_team_id)
            self.facade.store(team)

            msg = "Removed User from " + command_team

            # If the user is being removed from a team with special
            # permisisons, figure out a demotion strategy.
            demoted_level = None
            if command_team == self.config.github_team_leads:
                # If the user is currently an admin, we only demote this user
                # if it is currently NOT and admin team member
                if user.permissions_level == Permissions.admin \
                        and len(self.config.github_team_admin) > 0:
                    admins = get_team_by_name(self.facade,
                                              self.config.github_team_admin)
                    if not admins.has_member(user.github_id):
                        demoted_level = Permissions.member
                else:
                    demoted_level = Permissions.member
            if command_team == self.config.github_team_admin:
                # If the user is being removed from the admin team, we demote
                # this user to team_lead if this user is a member of the leads
                # team, otherwise we demote to member.
                demoted_level = Permissions.member
                if len(self.config.github_team_leads) > 0:
                    leads = get_team_by_name(self.facade,
                                             self.config.github_team_leads)
                    if leads.has_member(user.github_id):
                        demoted_level = Permissions.team_lead

            if demoted_level is not None:
                logging.info(f"Demoting {command_user} to member")
                user.permissions_level = demoted_level
                self.facade.store(user)
                msg += " and demoted user"
            ret = {'attachments': [team.get_attachment()], 'text': msg}
            return ret, 200

        except LookupError:
            return self.lookup_error, 200
        except GithubAPIException as e:
            logging.error("user removed unsuccessfully from team")
            return f"User removed unsuccessfully with " \
                   f"the following error: {e.data}", 200