Skip to content

Latest commit

 

History

History
257 lines (197 loc) · 9.22 KB

cEP-0015.md

File metadata and controls

257 lines (197 loc) · 9.22 KB

corobo Enhancement (security, tests and configurability)

Metadata
cEP 15
Version 1.0
Title corobo Enhancement (security, tests and configurability)
Authors Nitanshu Vashistha mailto:nitanshu.vzard@gmail.com
Status Proposed
Type Process

Abstract

This cEP describes the details of enhancement of corobo in terms of security, tests, configurability and the new plugins that are to be added to corobo as part of the GSoC Project.

Security

Security has been one of the major concerns due to some past experiences. These are the changes that will be implemented to the existing plugins in order to harden the security.

  1. Give developers the right to invite newcomers to the organization. This will involve changing the bot command to corobo invite <@username>.

  2. Remove the automatic invite due to Hello World message.

  3. Remove invite me bot command.

  4. Stop newcomers from getting assigned to more than one newcomer issue.

  5. Make all LabHub plugins require being a member of the organization. This will be done by creating a members_only decorator to check if the user is a member of team or not. The decorator can be used on any bot command which needs to be under the accessibility of an organization member only.

     def members_only(func):
         @wraps(func)
         def wrapper(*args, **kwargs):
             # plugin instance
             instance = args[0]
             msg = args[1]
             user = msg.frm.nick
             for team in instance.team_mapping().values():
                 if team.is_member(user):
                     yield from func(*args, **kwargs)
                     return
             yield ('You need to be a member of this organization'
                    ' to use this command.')
         return wrapper
  6. Add callback message to warn users who are spamming the room.

  7. Add bot command to ban a user from all rooms at once. The goal of this feature is to prevent spamming as someone with many accounts can't be stopped to join a room.

Tests

The default TestBase provided by Errbot is not enough for testing plugins like LabHub, which required intensive mocking.

This will involve making changes upstream in Errbot and extend the existing testing infrastructure to implement better testing for plugins like LabHub.

Errbot provides a pytest fixture testbot for testing purposes and to interact with the message queue. Extending the testbot and adding inject_mock method so that mocked objects can be injected into the plugin by passing a dictionary containing the PluginName, field_name and Mocked Object. The inject_mock method will inject a property on the plugin object and discard the assignments from the plugin itself.

def test_command(testbot):
    mocks = {'PluginName': {'field_name1': fakeObj1,
             'field_name2': fakeObj2}}
    testbot.inject_mock(mocks)
    assert 'something' in testbot.exec_command('!my_command')

Configurability

corobo has a potential to be used by other organizations for similar tasks like onboarding and automation. Currently, it is not configurable and many plugins are still very coala specific. Making it more configurable will allow other organizations to adapt corobo to cater their needs.

Making existing coala specific plugins generic for other organizations and letting them configure the plugins as per their needs will ensure configurability.

Errobot provides plugin configuration through the built-in !config command which can be used by other organizations to configure the plugins as per their needs.

List of Plugins which are coala specific and can be generalized:

  1. LabHub plugin commands are meant to work for a coala specific team names maintainers, developers, newcomers and coala repositories.

    Issue Link: coala/corobo#382

  2. explain currently uses hardcoded coala explanations. Configuring sub-directories explain/genric and explain/<org name> so that other organizations can include their culture-specific explanations.

  3. searchdocs uses API_DOCS and USER_DOCS links which are constant for coala. Other organizations will be able to initialize their own documentation links as per their requirement.

    Issue Link: coala/corobo#380

  4. community_stats will be moved out from LabHub as a separate plugin since it can also be used by other organizations for analytics overview.

    Issue Link: coala/corobo#361

  5. wolfram_alpha plugin currently uses an environment variable, it will be modified to use the configuration template.

    Issue Link: coala/corobo#383

  6. answer plugin use ANSWER_END environment variable as an endpoint of the answers microservice. It'll be configured so that other organizations can also adapt to it.

    Issue Link: coala/corobo#381

Configuration Sample

class LabHub(BotPlugin):
    def get_configuration_template(self):
        return {'GH_TEAM_NAMES': ['newcomers', 'developers', 'maintainers']}

    @botcmd
    def mycommand(self, mess, args):
        # oh I need my TEAM !
        gh_teams = self.config['GH_TEAM_NAMES']
<BOT_PREFIX> plugin config LabHub {'GH_TEAM_NAMES': ['newbies', 'devs', 'admins']}

Using the configuration templates means that the bot admin will have to configure the plugin every time the bot starts and since corobo is continuously deployed on each commit, reconfiguring again and again using the chat via a admin is not preferable.

This issue can be handled by using a Mixin to pre-configure the plugins through config.py file. By using DEFAULT_CONFIGS in config.py file to hold the configuration dictionaries for the plugins. Eg:

DEFAULT_CONFIG = {
    'LabHub': {
        'GH_TOKEN': os.getenv('GITHUB_TOKEN'),
        'GL_TOKEN': os.getenv('GITLAB_TOKEN'),
        'GH_ORG_NAME': os.getenv('GH_ORG_NAME'),
    },
    'WolframAlpha': {
        'WA_APP_ID': os.getenv('WA_TOKEN'),
    },
}

Configuration of the plugins will be done before their activation and if a plugin is already configured, the DefaultConfigMixin will autoconfigure the plugin from DEFAULT_CONFIGS.

class DefaultConfigMixin:
    @property
    def _default_config(self) -> Optional[Mapping]:
        if (self.bot_config.DEFAULT_CONFIG and self.name
        in self.bot_config.DEFAULT_CONFIG):
            return self.bot_config.DEFAULT_CONFIG[self.name]

    def __init__(self, bot, name=None) -> None:
        super().__init__(bot, name = name)
        default_config = self._default_config
        if default_config and not self.config:
            super().configure(default_config)

    def get_configuration_template(self) -> Mapping:
        default_config = self._default_config
        if default_config:
            return default_config
class MyPlugin(BotPlugin, DefaultConfigMixin):
      ...

Other possible ways to configure corobo were discussed here.

Use of activate instead of __init__ to configure plugins:

Currently, most of the plugins make use of the __init__ method for instead of activate for configuration. Since __init__ is executed at load time its failure will cause the plugin to not show up. Configuring the plugins through the activate method will remove this possibility.

Eg: labhub.py

    def __init__(self, bot, name=None):
        super().__init__(bot, name)

        teams = dict()
        try:
            gh = github3.login(token=os.environ.get('GH_TOKEN'))
            assert gh is not None
        except AssertionError:
            self.log.error('Cannot create github object, please check GH_TOKEN')
        else:
            self.GH3_ORG = gh.organization(self.GH_ORG_NAME)
            for team in self.GH3_ORG.teams():
                teams[team.name] = team

        self._teams = teams

        self.IGH = GitHub(GitHubToken(os.environ.get('GH_TOKEN')))
        self.IGL = GitLab(GitLabPrivateToken(os.environ.get('GL_TOKEN')))

        self.REPOS = dict()
    def activate(self):

        teams = dict()
        try:
            gh = github3.login(token=os.environ.get('GH_TOKEN'))
            assert gh is not None
        except AssertionError:
            self.log.error('Cannot create github object, please check GH_TOKEN')
        else:
            self.GH3_ORG = gh.organization(self.GH_ORG_NAME)
            for team in self.GH3_ORG.teams():
                teams[team.name] = team

        self._teams = teams

        self.IGH = GitHub(GitHubToken(os.environ.get('GH_TOKEN')))
        self.IGL = GitLab(GitLabPrivateToken(os.environ.get('GL_TOKEN')))

        super(LabHub, self).activate()