Beispiel #1
0
    async def _resolve_login(
            self, name: str) -> typing.List[github_types.GitHubLogin]:
        if not name:
            return []
        elif not isinstance(name, str):
            return [github_types.GitHubLogin(name)]
        elif name[0] != "@":
            return [github_types.GitHubLogin(name)]

        if "/" in name:
            organization, _, team_slug = name.partition("/")
            if not team_slug or "/" in team_slug:
                # Not a team slug
                return [github_types.GitHubLogin(name)]
            organization = github_types.GitHubLogin(organization[1:])
            if organization != self.pull["base"]["repo"]["owner"]["login"]:
                # TODO(sileht): We don't have the permissions, maybe we should report this
                return [github_types.GitHubLogin(name)]
            team_slug = github_types.GitHubTeamSlug(team_slug)
        else:
            team_slug = github_types.GitHubTeamSlug(name[1:])

        try:
            return await self.repository.installation.get_team_members(
                team_slug)
        except http.HTTPClientSideError as e:
            self.log.warning(
                "fail to get the organization, team or members",
                team=name,
                status_code=e.status_code,
                detail=e.message,
            )
        return [github_types.GitHubLogin(name)]
Beispiel #2
0
    def from_string(cls, value: str) -> "_GitHubTeam":
        if not value:
            raise voluptuous.Invalid("A GitHub team cannot be an empty string")

        # Remove leading @ if any:
        # This format is accepted in conditions so we're happy to accept it here too.
        if value[0] == "@":
            org, sep, team = value[1:].partition("/")
        else:
            org, sep, team = value.partition("/")

        if sep == "" and team == "":
            # Just a slug
            team = org
            final_org = None
        else:
            final_org = _check_GitHubLogin_format(org, "organization")

        if not team:
            raise voluptuous.Invalid("A GitHub team cannot be an empty string")

        if ("/" in team or team[0] == "-" or team[-1] == "-"
                or not team.isascii()
                or not team.replace("-", "").replace("_", "").isalnum()):
            raise voluptuous.Invalid("GitHub team contains invalid characters")

        return cls(github_types.GitHubTeamSlug(team), final_org, value)
Beispiel #3
0
async def _resolve_login(
    ctxt: context.Context, name: str
) -> typing.List[github_types.GitHubLogin]:
    if not name:
        return []
    elif not isinstance(name, str):
        return [github_types.GitHubLogin(name)]
    elif name[0] != "@":
        return [github_types.GitHubLogin(name)]

    if "/" in name:
        organization, _, team_slug = name.partition("/")
        if not team_slug or "/" in team_slug:
            raise LiveResolutionFailure(f"Team `{name}` is invalid")
        organization = github_types.GitHubLogin(organization[1:])
        expected_organization = ctxt.pull["base"]["repo"]["owner"]["login"]
        if organization != expected_organization:
            raise LiveResolutionFailure(
                f"Team `{name}` is not part of the organization `{expected_organization}`"
            )
        team_slug = github_types.GitHubTeamSlug(team_slug)
    else:
        team_slug = github_types.GitHubTeamSlug(name[1:])

    try:
        return await ctxt.repository.installation.get_team_members(team_slug)
    except http.HTTPNotFound:
        raise LiveResolutionFailure(f"Team `{name}` does not exist")
    except http.HTTPClientSideError as e:
        ctxt.log.warning(
            "fail to get the organization, team or members",
            team=name,
            status_code=e.status_code,
            detail=e.message,
        )
        raise LiveResolutionFailure(
            f"Failed retrieve team `{name}`, details: {e.message}"
        )
Beispiel #4
0
async def test_team_permission_cache(redis_cache: utils.RedisCache) -> None:
    class FakeClient(github.AsyncGithubInstallationClient):
        called: int

        def __init__(self, owner, repo):
            super().__init__(auth=None)
            self.owner = owner
            self.repo = repo
            self.called = 0

        async def get(self, url, *args, **kwargs):
            self.called += 1
            if (url ==
                    f"/orgs/{self.owner}/teams/team-ok/repos/{self.owner}/{self.repo}"
                ):
                return {}
            elif (url ==
                  f"/orgs/{self.owner}/teams/team-nok/repos/{self.owner}/{self.repo}"
                  ):
                raise http.HTTPNotFound(message="Not found",
                                        request=mock.ANY,
                                        response=mock.ANY)
            elif (url ==
                  f"/orgs/{self.owner}/teams/team-also-nok/repos/{self.owner}/{self.repo}"
                  ):
                raise http.HTTPNotFound(message="Not found",
                                        request=mock.ANY,
                                        response=mock.ANY)

            raise ValueError(f"Unknown test URL `{url}`")

    gh_owner = github_types.GitHubAccount({
        "id":
        github_types.GitHubAccountIdType(123),
        "login":
        github_types.GitHubLogin("jd"),
        "type":
        "User",
        "avatar_url":
        "",
    })

    gh_repo = github_types.GitHubRepository({
        "id":
        github_types.GitHubRepositoryIdType(0),
        "owner":
        gh_owner,
        "full_name":
        "",
        "archived":
        False,
        "url":
        "",
        "default_branch":
        github_types.GitHubRefType(""),
        "name":
        github_types.GitHubRepositoryName("test"),
        "private":
        False,
    })

    team_slug1 = github_types.GitHubTeamSlug("team-ok")
    team_slug2 = github_types.GitHubTeamSlug("team-nok")
    team_slug3 = github_types.GitHubTeamSlug("team-also-nok")

    sub = subscription.Subscription(redis_cache, 0, False, "", frozenset())
    client = FakeClient(gh_owner["login"], gh_repo["name"])
    installation = context.Installation(gh_owner["id"], gh_owner["login"], sub,
                                        client, redis_cache)
    repository = context.Repository(installation, gh_repo["name"],
                                    gh_repo["id"])
    assert client.called == 0
    assert await repository.team_has_read_permission(team_slug1)
    assert client.called == 1
    assert await repository.team_has_read_permission(team_slug1)
    assert client.called == 1
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 2
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 2
    assert not await repository.team_has_read_permission(team_slug3)
    assert client.called == 3

    gh_repo = github_types.GitHubRepository({
        "id":
        github_types.GitHubRepositoryIdType(1),
        "owner":
        gh_owner,
        "full_name":
        "",
        "archived":
        False,
        "url":
        "",
        "default_branch":
        github_types.GitHubRefType(""),
        "name":
        github_types.GitHubRepositoryName("test2"),
        "private":
        False,
    })

    client = FakeClient(gh_owner["login"], gh_repo["name"])
    installation = context.Installation(gh_owner["id"], gh_owner["login"], sub,
                                        client, redis_cache)
    repository = context.Repository(installation, gh_repo["name"],
                                    gh_repo["id"])
    assert client.called == 0
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 1
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 1
    assert await repository.team_has_read_permission(team_slug1)
    assert client.called == 2
    await context.Repository.clear_team_permission_cache_for_repo(
        redis_cache, gh_owner, gh_repo)
    assert await repository.team_has_read_permission(team_slug1)
    assert client.called == 3
    assert not await repository.team_has_read_permission(team_slug3)
    assert client.called == 4
    await context.Repository.clear_team_permission_cache_for_org(
        redis_cache, gh_owner)
    assert not await repository.team_has_read_permission(team_slug3)
    assert client.called == 5
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 6
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 6
    await context.Repository.clear_team_permission_cache_for_team(
        redis_cache, gh_owner, team_slug2)
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 7
Beispiel #5
0
async def test_team_members_cache(redis_cache: utils.RedisCache) -> None:
    class FakeClient(github.AsyncGithubInstallationClient):
        called: int

        def __init__(self, owner):
            super().__init__(auth=None)
            self.owner = owner
            self.called = 0

        async def items(self, url, *args, **kwargs):
            self.called += 1
            if url == f"/orgs/{self.owner}/teams/team1/members":
                yield {"login": "******"}
                yield {"login": "******"}
            elif url == f"/orgs/{self.owner}/teams/team2/members":
                yield {"login": "******"}
                yield {"login": "******"}
            elif url == f"/orgs/{self.owner}/teams/team3/members":
                return
            else:
                raise ValueError(
                    f"Unknown test URL `{url}` for repo {self.repo}")

    gh_owner = github_types.GitHubAccount({
        "id":
        github_types.GitHubAccountIdType(123),
        "login":
        github_types.GitHubLogin("jd"),
        "type":
        "User",
        "avatar_url":
        "",
    })

    team_slug1 = github_types.GitHubTeamSlug("team1")
    team_slug2 = github_types.GitHubTeamSlug("team2")
    team_slug3 = github_types.GitHubTeamSlug("team3")

    sub = subscription.Subscription(redis_cache, 0, False, "", frozenset())
    client = FakeClient(gh_owner["login"])
    installation = context.Installation(gh_owner["id"], gh_owner["login"], sub,
                                        client, redis_cache)
    assert client.called == 0
    assert (await installation.get_team_members(team_slug1)) == [
        "member1", "member2"
    ]
    assert client.called == 1
    assert (await installation.get_team_members(team_slug1)) == [
        "member1", "member2"
    ]
    assert client.called == 1
    assert (await installation.get_team_members(team_slug2)) == [
        "member3", "member4"
    ]
    assert client.called == 2
    assert (await installation.get_team_members(team_slug2)) == [
        "member3", "member4"
    ]
    assert client.called == 2
    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 3
    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 3
    await installation.clear_team_members_cache_for_team(
        redis_cache, gh_owner, github_types.GitHubTeamSlug(team_slug2))
    assert (await installation.get_team_members(team_slug2)) == [
        "member3", "member4"
    ]
    assert client.called == 4
    assert (await installation.get_team_members(team_slug2)) == [
        "member3", "member4"
    ]
    assert client.called == 4
    await installation.clear_team_members_cache_for_org(redis_cache, gh_owner)
    assert (await installation.get_team_members(team_slug1)) == [
        "member1", "member2"
    ]
    assert client.called == 5
    assert (await installation.get_team_members(team_slug1)) == [
        "member1", "member2"
    ]
    assert client.called == 5
    assert (await installation.get_team_members(team_slug2)) == [
        "member3", "member4"
    ]
    assert client.called == 6
    assert (await installation.get_team_members(team_slug2)) == [
        "member3", "member4"
    ]
    assert client.called == 6
    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 7
    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 7
Beispiel #6
0
async def test_team_permission_cache(redis_cache: utils.RedisCache) -> None:
    class FakeClient(github.AsyncGithubInstallationClient):
        called: int

        def __init__(self, owner: str, repo: str) -> None:
            super().__init__(auth=None)  # type: ignore[arg-type]
            self.owner = owner
            self.repo = repo
            self.called = 0

        async def get(self, url: str, *args: typing.Any, **kwargs: typing.Any) -> typing.Any:  # type: ignore[override]
            self.called += 1
            if (
                url
                == f"/orgs/{self.owner}/teams/team-ok/repos/{self.owner}/{self.repo}"
            ):
                return {}
            elif (
                url
                == f"/orgs/{self.owner}/teams/team-nok/repos/{self.owner}/{self.repo}"
            ):
                raise http.HTTPNotFound(
                    message="Not found", request=mock.ANY, response=mock.ANY
                )
            elif (
                url
                == f"/orgs/{self.owner}/teams/team-also-nok/repos/{self.owner}/{self.repo}"
            ):
                raise http.HTTPNotFound(
                    message="Not found", request=mock.ANY, response=mock.ANY
                )

            raise ValueError(f"Unknown test URL `{url}`")

    gh_owner = github_types.GitHubAccount(
        {
            "id": github_types.GitHubAccountIdType(123),
            "login": github_types.GitHubLogin("jd"),
            "type": "User",
            "avatar_url": "",
        }
    )

    gh_repo = github_types.GitHubRepository(
        {
            "id": github_types.GitHubRepositoryIdType(0),
            "owner": gh_owner,
            "full_name": "",
            "archived": False,
            "url": "",
            "html_url": "",
            "default_branch": github_types.GitHubRefType(""),
            "name": github_types.GitHubRepositoryName("test"),
            "private": False,
        }
    )
    installation_json = github_types.GitHubInstallation(
        {
            "id": github_types.GitHubInstallationIdType(12345),
            "target_type": gh_owner["type"],
            "permissions": {},
            "account": gh_owner,
        }
    )

    team_slug1 = github_types.GitHubTeamSlug("team-ok")
    team_slug2 = github_types.GitHubTeamSlug("team-nok")
    team_slug3 = github_types.GitHubTeamSlug("team-also-nok")

    sub = subscription.Subscription(
        redis_cache, 0, "", frozenset([subscription.Features.PUBLIC_REPOSITORY])
    )
    client = FakeClient(gh_owner["login"], gh_repo["name"])
    installation = context.Installation(
        installation_json, sub, client, redis_cache, mock.Mock()
    )
    repository = context.Repository(installation, gh_repo)
    assert client.called == 0
    assert await repository.team_has_read_permission(team_slug1)
    assert client.called == 1
    assert await repository.team_has_read_permission(team_slug1)
    assert client.called == 1
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 2
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 2
    assert not await repository.team_has_read_permission(team_slug3)
    assert client.called == 3

    gh_repo = github_types.GitHubRepository(
        {
            "id": github_types.GitHubRepositoryIdType(1),
            "owner": gh_owner,
            "full_name": "",
            "archived": False,
            "url": "",
            "html_url": "",
            "default_branch": github_types.GitHubRefType(""),
            "name": github_types.GitHubRepositoryName("test2"),
            "private": False,
        }
    )

    client = FakeClient(gh_owner["login"], gh_repo["name"])
    installation = context.Installation(
        installation_json, sub, client, redis_cache, mock.Mock()
    )
    repository = context.Repository(installation, gh_repo)
    assert client.called == 0
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 1
    # From local cache
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 1
    # From redis
    repository._caches.team_has_read_permission.clear()
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 1
    assert await repository.team_has_read_permission(team_slug1)
    assert client.called == 2
    await context.Repository.clear_team_permission_cache_for_repo(
        redis_cache, gh_owner, gh_repo
    )
    repository._caches.team_has_read_permission.clear()
    assert await repository.team_has_read_permission(team_slug1)
    assert client.called == 3
    assert not await repository.team_has_read_permission(team_slug3)
    assert client.called == 4
    await context.Repository.clear_team_permission_cache_for_org(redis_cache, gh_owner)
    repository._caches.team_has_read_permission.clear()
    assert not await repository.team_has_read_permission(team_slug3)
    assert client.called == 5
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 6
    # From local cache
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 6
    # From redis
    repository._caches.team_has_read_permission.clear()
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 6
    repository._caches.team_has_read_permission.clear()
    await context.Repository.clear_team_permission_cache_for_team(
        redis_cache, gh_owner, team_slug2
    )
    repository._caches.team_has_read_permission.clear()
    assert not await repository.team_has_read_permission(team_slug2)
    assert client.called == 7
Beispiel #7
0
async def test_team_members_cache(redis_cache: utils.RedisCache) -> None:
    class FakeClient(github.AsyncGithubInstallationClient):
        called: int

        def __init__(self, owner: str) -> None:
            super().__init__(auth=None)  # type: ignore[arg-type]
            self.owner = owner
            self.called = 0

        async def items(self, url, *args, **kwargs):
            self.called += 1
            if url == f"/orgs/{self.owner}/teams/team1/members":
                yield {"login": "******"}
                yield {"login": "******"}
            elif url == f"/orgs/{self.owner}/teams/team2/members":
                yield {"login": "******"}
                yield {"login": "******"}
            elif url == f"/orgs/{self.owner}/teams/team3/members":
                return
            else:
                raise ValueError(f"Unknown test URL `{url}` for repo {self.repo}")

    gh_owner = github_types.GitHubAccount(
        {
            "id": github_types.GitHubAccountIdType(123),
            "login": github_types.GitHubLogin("jd"),
            "type": "User",
            "avatar_url": "",
        }
    )
    installation_json = github_types.GitHubInstallation(
        {
            "id": github_types.GitHubInstallationIdType(12345),
            "target_type": gh_owner["type"],
            "permissions": {},
            "account": gh_owner,
        }
    )

    team_slug1 = github_types.GitHubTeamSlug("team1")
    team_slug2 = github_types.GitHubTeamSlug("team2")
    team_slug3 = github_types.GitHubTeamSlug("team3")

    sub = subscription.Subscription(
        redis_cache, 0, "", frozenset([subscription.Features.PUBLIC_REPOSITORY])
    )
    client = FakeClient(gh_owner["login"])
    installation = context.Installation(
        installation_json, sub, client, redis_cache, mock.Mock()
    )
    assert client.called == 0
    assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"]
    assert client.called == 1
    assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"]
    assert client.called == 1
    assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"]
    assert client.called == 2
    # From local cache
    assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"]
    assert client.called == 2
    # From redis
    installation._caches.team_members.clear()
    assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"]
    assert client.called == 2

    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 3
    # From local cache
    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 3
    # From redis
    installation._caches.team_members.clear()
    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 3

    await installation.clear_team_members_cache_for_team(
        redis_cache, gh_owner, github_types.GitHubTeamSlug(team_slug2)
    )
    installation._caches.team_members.clear()

    assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"]
    assert client.called == 4
    # From local cache
    assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"]
    assert client.called == 4
    # From redis
    installation._caches.team_members.clear()
    assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"]
    assert client.called == 4

    await installation.clear_team_members_cache_for_org(redis_cache, gh_owner)
    installation._caches.team_members.clear()

    assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"]
    assert client.called == 5
    # From local cache
    assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"]
    assert client.called == 5
    # From redis
    installation._caches.team_members.clear()
    assert (await installation.get_team_members(team_slug1)) == ["member1", "member2"]
    assert client.called == 5

    assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"]
    assert client.called == 6
    # From local cache
    assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"]
    assert client.called == 6
    # From redis
    installation._caches.team_members.clear()
    assert (await installation.get_team_members(team_slug2)) == ["member3", "member4"]
    assert client.called == 6
    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 7
    # From local cache
    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 7
    # From redis
    installation._caches.team_members.clear()
    assert (await installation.get_team_members(team_slug3)) == []
    assert client.called == 7
Beispiel #8
0
async def build_fake_context(
    number: github_types.GitHubPullRequestNumber,
    *,
    repository: context.Repository,
    **kwargs: typing.Dict[str, typing.Any],
) -> context.Context:
    pull_request_author = github_types.GitHubAccount(
        {
            "id": github_types.GitHubAccountIdType(123),
            "type": "User",
            "login": github_types.GitHubLogin("contributor"),
            "avatar_url": "",
        }
    )

    pull: github_types.GitHubPullRequest = {
        "node_id": "42",
        "locked": False,
        "assignees": [],
        "requested_reviewers": [
            {
                "id": github_types.GitHubAccountIdType(123),
                "type": "User",
                "login": github_types.GitHubLogin("jd"),
                "avatar_url": "",
            },
            {
                "id": github_types.GitHubAccountIdType(456),
                "type": "User",
                "login": github_types.GitHubLogin("sileht"),
                "avatar_url": "",
            },
        ],
        "requested_teams": [
            {"slug": github_types.GitHubTeamSlug("foobar")},
            {"slug": github_types.GitHubTeamSlug("foobaz")},
        ],
        "milestone": None,
        "title": "awesome",
        "body": "",
        "created_at": github_types.ISODateTimeType("2021-06-01T18:41:39Z"),
        "closed_at": None,
        "updated_at": github_types.ISODateTimeType("2021-06-01T18:41:39Z"),
        "id": github_types.GitHubPullRequestId(123),
        "maintainer_can_modify": True,
        "user": pull_request_author,
        "labels": [],
        "rebaseable": True,
        "draft": False,
        "merge_commit_sha": None,
        "number": number,
        "commits": 1,
        "mergeable_state": "clean",
        "mergeable": True,
        "state": "open",
        "changed_files": 1,
        "head": {
            "sha": github_types.SHAType("the-head-sha"),
            "label": f"{pull_request_author['login']}:feature-branch",
            "ref": github_types.GitHubRefType("feature-branch"),
            "repo": {
                "id": github_types.GitHubRepositoryIdType(123),
                "default_branch": github_types.GitHubRefType("main"),
                "name": github_types.GitHubRepositoryName("mergify-engine"),
                "full_name": "contributor/mergify-engine",
                "archived": False,
                "private": False,
                "owner": pull_request_author,
                "url": "https://api.github.com/repos/contributor/mergify-engine",
                "html_url": "https://github.com/contributor/mergify-engine",
            },
            "user": pull_request_author,
        },
        "merged": False,
        "merged_by": None,
        "merged_at": None,
        "html_url": "https://...",
        "base": {
            "label": "mergify_engine:main",
            "ref": github_types.GitHubRefType("main"),
            "repo": repository.repo,
            "sha": github_types.SHAType("the-base-sha"),
            "user": repository.repo["owner"],
        },
    }
    pull.update(kwargs)  # type: ignore
    return await context.Context.create(repository, pull)