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

SwissDataScienceCenter / renku-data-services / 9446003648

10 Jun 2024 09:35AM UTC coverage: 90.298% (+0.06%) from 90.239%
9446003648

Pull #248

github

web-flow
Merge 9ff3e8a6c into 1e340ea36
Pull Request #248: feat: add support for bitbucket

40 of 46 new or added lines in 3 files covered. (86.96%)

4 existing lines in 4 files now uncovered.

8488 of 9400 relevant lines covered (90.3%)

1.6 hits per line

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

78.05
/components/renku_data_services/connected_services/provider_adapters.py
1
"""Adapters for each kind of OAuth2 client."""
2✔
2

3
from abc import ABC, abstractmethod
2✔
4
from urllib.parse import urljoin, urlparse, urlunparse
2✔
5

6
from httpx import Response
2✔
7

8
from renku_data_services import errors
2✔
9
from renku_data_services.connected_services import external_models, models
2✔
10
from renku_data_services.connected_services import orm as schemas
2✔
11
from renku_data_services.connected_services.apispec import ProviderKind
2✔
12

13

14
class ProviderAdapter(ABC):
2✔
15
    """Defines the functionality of OAuth2 client adapters."""
2✔
16

17
    def __init__(self, client_url: str) -> None:
2✔
18
        self.client_url = client_url
1✔
19

20
    @property
2✔
21
    @abstractmethod
2✔
22
    def authorization_url(self) -> str:
2✔
23
        """The authorization URL for the OAuth2 protocol."""
24
        ...
×
25

26
    @property
2✔
27
    @abstractmethod
2✔
28
    def token_endpoint_url(self) -> str:
2✔
29
        """The token endpoint URL for the OAuth2 protocol."""
30
        ...
×
31

32
    @property
2✔
33
    @abstractmethod
2✔
34
    def api_url(self) -> str:
2✔
35
        """The URL used for API calls on the Resource Server."""
36
        ...
×
37

38
    @property
2✔
39
    def api_common_headers(self) -> dict[str, str] | None:
2✔
40
        """The HTTP headers used for API calls on the Resource Server."""
41
        return None
1✔
42

43
    @abstractmethod
2✔
44
    def api_validate_account_response(self, response: Response) -> models.ConnectedAccount:
2✔
45
        """Validates and returns the connected account response from the Resource Server."""
46
        ...
×
47

48

49
class GitLabAdapter(ProviderAdapter):
2✔
50
    """Adapter for GitLab OAuth2 clients."""
2✔
51

52
    @property
2✔
53
    def authorization_url(self) -> str:
2✔
54
        """The authorization URL for the OAuth2 protocol."""
55
        return urljoin(self.client_url, "oauth/authorize")
1✔
56

57
    @property
2✔
58
    def token_endpoint_url(self) -> str:
2✔
59
        """The token endpoint URL for the OAuth2 protocol."""
60
        return urljoin(self.client_url, "oauth/token")
1✔
61

62
    @property
2✔
63
    def api_url(self) -> str:
2✔
64
        """The URL used for API calls on the Resource Server."""
65
        return urljoin(self.client_url, "api/v4/")
1✔
66

67
    def api_validate_account_response(self, response: Response) -> models.ConnectedAccount:
2✔
68
        """Validates and returns the connected account response from the Resource Server."""
69
        return external_models.GitLabConnectedAccount.model_validate(response.json()).to_connected_account()
1✔
70

71

72
class GitHubAdapter(ProviderAdapter):
2✔
73
    """Adapter for GitLab OAuth2 clients."""
2✔
74

75
    @property
2✔
76
    def authorization_url(self) -> str:
2✔
77
        """The authorization URL for the OAuth2 protocol."""
78
        return urljoin(self.client_url, "login/oauth/authorize")
×
79

80
    @property
2✔
81
    def token_endpoint_url(self) -> str:
2✔
82
        """The token endpoint URL for the OAuth2 protocol."""
83
        return urljoin(self.client_url, "login/oauth/access_token")
×
84

85
    @property
2✔
86
    def api_url(self) -> str:
2✔
87
        """The URL used for API calls on the Resource Server."""
88
        url = urlparse(self.client_url)
×
89
        url = url._replace(netloc=f"api.{url.netloc}")
×
90
        return urlunparse(url)
×
91

92
    @property
2✔
93
    def api_common_headers(self) -> dict[str, str] | None:
2✔
94
        """The HTTP headers used for API calls on the Resource Server."""
95
        return {
×
96
            "Accept": "application/vnd.github+json",
97
            "X-GitHub-Api-Version": "2022-11-28",
98
        }
99

100
    def api_validate_account_response(self, response: Response) -> models.ConnectedAccount:
2✔
101
        """Validates and returns the connected account response from the Resource Server."""
102
        return external_models.GitHubConnectedAccount.model_validate(response.json()).to_connected_account()
×
103

104

105
class BitBucketAdapter(ProviderAdapter):
2✔
106
    """Adapter for BitBucket OAuth2 clients."""
2✔
107

108
    @property
2✔
109
    def authorization_url(self) -> str:
2✔
110
        """The authorization URL for the OAuth2 protocol."""
NEW
111
        return urljoin(self.client_url, "site/oauth2/authorize")
×
112

113
    @property
2✔
114
    def token_endpoint_url(self) -> str:
2✔
115
        """The token endpoint URL for the OAuth2 protocol."""
NEW
116
        return urljoin(self.client_url, "site/oauth2/access_token")
×
117

118
    @property
2✔
119
    def api_url(self) -> str:
2✔
120
        """The URL used for API calls on the Resource Server."""
NEW
121
        url = urlparse(self.client_url)
×
NEW
122
        url = url._replace(netloc=f"api.{url.netloc}")
×
NEW
123
        return urljoin(urlunparse(url), "2.0")
×
124

125
    def api_validate_account_response(self, response: Response) -> models.ConnectedAccount:
2✔
126
        """Validates and returns the connected account response from the Resource Server."""
NEW
127
        raise NotImplementedError()
×
128

129

130
_adapter_map: dict[ProviderKind, type[ProviderAdapter]] = {
2✔
131
    ProviderKind.gitlab: GitLabAdapter,
132
    ProviderKind.github: GitHubAdapter,
133
    ProviderKind.bitbucket: BitBucketAdapter,
134
}
135

136

137
def get_provider_adapter(client: schemas.OAuth2ClientORM) -> ProviderAdapter:
2✔
138
    """Returns a new ProviderAdapter instance corresponding to the given client."""
139
    global _adapter_map
140

141
    if not client.url:
1✔
142
        raise errors.ValidationError(message=f"URL not defined for provider {client.id}.")
×
143

144
    adapter_class = _adapter_map[client.kind]
1✔
145
    return adapter_class(client_url=client.url)
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

© 2025 Coveralls, Inc