• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SwissDataScienceCenter / renku-data-services / 6652464668

26 Oct 2023 09:57AM UTC coverage: 86.759% (+0.004%) from 86.755%
6652464668

push

gihub-action

web-flow
fix: handle listing storage for anonymous users (#63)

103 of 103 new or added lines in 10 files covered. (100.0%)

2431 of 2802 relevant lines covered (86.76%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/components/renku_data_services/git/gitlab.py
1
"""Gitlab API."""
1✔
2
from dataclasses import dataclass, field
1✔
3
from typing import List
1✔
4

5
import httpx
1✔
6

7
from renku_data_services.base_models import APIUser, GitlabAccessLevel
1✔
8
from renku_data_services.errors import errors
1✔
9
from renku_data_services.utils.core import get_ssl_context
1✔
10

11

12
@dataclass(kw_only=True)
1✔
13
class GitlabAPI:
1✔
14
    """Adapter for interacting with the gitlab API."""
1✔
15

16
    gitlab_url: str
1✔
17
    gitlab_graphql_url: str = field(init=False)
1✔
18

19
    def __post_init__(self):
1✔
20
        """Sets the graphql url for gitlab."""
21
        gitlab_url = self.gitlab_url
1✔
22

23
        if not gitlab_url.startswith("http") and "://" not in gitlab_url:
1✔
24
            raise errors.ConfigurationError(message=f"Gitlab URL should start with 'http(s)://', got: {gitlab_url}")
×
25

26
        gitlab_url = gitlab_url.rstrip("/")
1✔
27

28
        self.gitlab_graphql_url = f"{gitlab_url}/api/graphql"
1✔
29

30
    async def filter_projects_by_access_level(
1✔
31
        self, user: APIUser, project_ids: List[str], min_access_level: GitlabAccessLevel
32
    ) -> List[str]:
33
        """Filter projects this user can access in gitlab with at least access level."""
34

35
        if not user.access_token or not user.name:
1✔
36
            return []
×
37
        header = {"Authorization": f"Bearer {user.access_token}", "Content-Type": "application/json"}
1✔
38
        ids = ",".join(f'"gid://gitlab/Project/{id}"' for id in project_ids)
1✔
39
        query_body = f"""
1✔
40
                    pageInfo {{
41
                      hasNextPage
42
                    }}
43
                    nodes {{
44
                        id
45
                        projectMembers(search: "{user.name}") {{
46
                            nodes {{
47
                                user {{
48
                                    id
49
                                    name
50
                                }}
51
                                accessLevel {{
52
                                    integerValue
53
                                }}
54
                            }}
55
                        }}
56
                    }}
57
        """
58
        body = {
1✔
59
            "query": f"""{{
60
                projects(ids: [{ids}]) {{
61
                    {query_body}
62
                }}
63
            }}
64
            """
65
        }
66

67
        async def _query_gitlab_graphql(body, header):
1✔
68
            async with httpx.AsyncClient(verify=get_ssl_context()) as client:
1✔
69
                resp = await client.post(self.gitlab_graphql_url, json=body, headers=header, timeout=10)
1✔
70
            if resp.status_code != 200:
1✔
71
                raise errors.BaseError(message=f"Error querying Gitlab api {self.gitlab_graphql_url}: {resp.text}")
×
72
            result = resp.json()
1✔
73

74
            if "data" not in result or "projects" not in result["data"]:
1✔
75
                raise errors.BaseError(message=f"Got unexpected response from Gitlab: {result}")
×
76
            return result
1✔
77

78
        resp_body = await _query_gitlab_graphql(body, header)
1✔
79
        result: List[str] = []
1✔
80

81
        def _process_projects(resp_body, min_access_level, result):
1✔
82
            for project in resp_body["data"]["projects"]["nodes"]:
1✔
83
                if min_access_level != GitlabAccessLevel.PUBLIC:
1✔
84
                    if not project["projectMembers"]["nodes"]:
1✔
85
                        continue
1✔
86
                    if min_access_level == GitlabAccessLevel.ADMIN:
1✔
87
                        max_level = max(
1✔
88
                            n["accessLevel"]["integerValue"]
89
                            for n in project["projectMembers"]["nodes"]
90
                            if n["user"]["id"].rsplit("/", maxsplit=1)[-1] == user.id
91
                        )
92
                        if max_level < 30:
1✔
93
                            continue
1✔
94
                result.append(project["id"].rsplit("/", maxsplit=1)[-1])
1✔
95

96
        _process_projects(resp_body, min_access_level, result)
1✔
97
        page_info = resp_body["data"]["projects"]["pageInfo"]
1✔
98
        while page_info["hasNextPage"]:
1✔
99
            cursor = page_info["endCursor"]
×
100
            body = {
×
101
                "query": f"""{{
102
                    projects(ids: [{ids}], after: "{cursor}") {{
103
                        {query_body}
104
                    }}
105
                }}
106
                """
107
            }
108
            resp_body = await _query_gitlab_graphql(body, header)
×
109
            page_info = resp_body["data"]["projects"]["pageInfo"]
×
110
            _process_projects(resp_body, min_access_level, result)
×
111

112
        return result
1✔
113

114

115
@dataclass(kw_only=True)
1✔
116
class DummyGitlabAPI:
1✔
117
    """Dummy gitlab API where the user with name John Doe has admin access to project 123456 and member access to 999999."""
1✔
118

119
    _store = {
1✔
120
        "John Doe": {
121
            GitlabAccessLevel.MEMBER: ["999999", "123456"],
122
            GitlabAccessLevel.ADMIN: ["123456"],
123
        },
124
    }
125

126
    async def filter_projects_by_access_level(
1✔
127
        self, user: APIUser, project_ids: List[str], min_access_level: GitlabAccessLevel
128
    ) -> List[str]:
129
        """Filter projects this user can access in gitlab with at least access level."""
130
        if not user.access_token or not user.name:
1✔
131
            return []
×
132
        if min_access_level == GitlabAccessLevel.PUBLIC:
1✔
133
            return []
×
134
        user_projects = self._store.get(user.name, {}).get(min_access_level, [])
1✔
135
        return [p for p in project_ids if p in user_projects]
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc