This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from allauth.socialaccount.providers.base import ProviderAccount
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
class GitLabAccount(ProviderAccount):
def get_profile_url(self):
return self.account.extra_data.get("web_url")
def get_avatar_url(self):
return self.account.extra_data.get("avatar_url")
def to_str(self):
dflt = super(GitLabAccount, self).to_str()
return self.account.extra_data.get("name", dflt)
class GitLabProvider(OAuth2Provider):
id = "gitlab"
name = "GitLab"
account_class = GitLabAccount
def get_default_scope(self):
return ["read_user"]
def extract_uid(self, data):
return str(data["id"])
def extract_common_fields(self, data):
return dict(
email=data.get("email"),
username=data.get("username"),
name=data.get("name"),
)
provider_classes = [GitLabProvider]

View File

@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
import json
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.providers.gitlab.provider import GitLabProvider
from allauth.socialaccount.providers.oauth2.client import OAuth2Error
from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from .views import _check_errors
class GitLabTests(OAuth2TestsMixin, TestCase):
provider_id = GitLabProvider.id
_uid = 2
def get_mocked_response(self):
return MockedResponse(
200,
"""
{
"avatar_url": "https://secure.gravatar.com/avatar/123",
"bio": null,
"can_create_group": true,
"can_create_project": true,
"color_scheme_id": 5,
"confirmed_at": "2015-03-02T16:53:58.370Z",
"created_at": "2015-03-02T16:53:58.885Z",
"current_sign_in_at": "2018-06-12T18:44:49.985Z",
"email": "mr.bob@gitlab.example.com",
"external": false,
"id": 2,
"identities": [],
"last_activity_on": "2018-06-11",
"last_sign_in_at": "2018-05-31T14:59:44.527Z",
"linkedin": "",
"location": null,
"name": "Mr Bob",
"organization": null,
"projects_limit": 10,
"shared_runners_minutes_limit": 2000,
"skype": "",
"state": "active",
"theme_id": 6,
"twitter": "mrbob",
"two_factor_enabled": true,
"username": "mr.bob",
"web_url": "https://gitlab.example.com/u/mr.bob",
"website_url": ""
}
""",
)
def test_valid_response(self):
data = {"id": 12345}
response = MockedResponse(200, json.dumps(data))
self.assertEqual(_check_errors(response), data)
def test_invalid_data(self):
response = MockedResponse(200, json.dumps({}))
with self.assertRaises(OAuth2Error):
# No id, raises
_check_errors(response)
def test_account_invalid_response(self):
body = (
"403 Forbidden - You (@domain.com) must accept the Terms of "
"Service in order to perform this action. Please access GitLab "
"from a web browser to accept these terms."
)
response = MockedResponse(403, body)
# GitLab allow users to login with their API and provides
# an error requiring the user to accept the Terms of Service.
# see: https://gitlab.com/gitlab-org/gitlab-foss/-/issues/45849
with self.assertRaises(OAuth2Error):
# no id, 4xx code, raises
_check_errors(response)
def test_error_response(self):
body = "403 Forbidden"
response = MockedResponse(403, body)
with self.assertRaises(OAuth2Error):
# no id, 4xx code, raises
_check_errors(response)
def test_invalid_response(self):
response = MockedResponse(200, json.dumps({}))
with self.assertRaises(OAuth2Error):
# No id, raises
_check_errors(response)
def test_bad_response(self):
response = MockedResponse(400, json.dumps({}))
with self.assertRaises(OAuth2Error):
# bad json, raises
_check_errors(response)
def test_extra_data(self):
self.login(self.get_mocked_response())
account = SocialAccount.objects.get(uid=str(self._uid))
self.assertEqual(account.extra_data["id"], self._uid)

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from allauth.socialaccount.providers.gitlab.provider import GitLabProvider
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
urlpatterns = default_urlpatterns(GitLabProvider)

View File

@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
import requests
from allauth.socialaccount import app_settings
from allauth.socialaccount.providers.gitlab.provider import GitLabProvider
from allauth.socialaccount.providers.oauth2.client import OAuth2Error
from allauth.socialaccount.providers.oauth2.views import (
OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView,
)
def _check_errors(response):
# 403 error's are presented as user-facing errors
if response.status_code == 403:
msg = response.content
raise OAuth2Error("Invalid data from GitLab API: %r" % (msg))
try:
data = response.json()
except ValueError: # JSONDecodeError on py3
raise OAuth2Error("Invalid JSON from GitLab API: %r" % (response.text))
if response.status_code >= 400 or "error" in data:
# For errors, we expect the following format:
# {"error": "error_name", "error_description": "Oops!"}
# For example, if the token is not valid, we will get:
# {"message": "status_code - message"}
error = data.get("error", "") or response.status_code
desc = data.get("error_description", "") or data.get("message", "")
raise OAuth2Error("GitLab error: %s (%s)" % (error, desc))
# The expected output from the API follows this format:
# {"id": 12345, ...}
if "id" not in data:
# If the id is not present, the output is not usable (no UID)
raise OAuth2Error("Invalid data from GitLab API: %r" % (data))
return data
class GitLabOAuth2Adapter(OAuth2Adapter):
provider_id = GitLabProvider.id
provider_default_url = "https://gitlab.com"
provider_api_version = "v4"
settings = app_settings.PROVIDERS.get(provider_id, {})
provider_base_url = settings.get("GITLAB_URL", provider_default_url)
access_token_url = "{0}/oauth/token".format(provider_base_url)
authorize_url = "{0}/oauth/authorize".format(provider_base_url)
profile_url = "{0}/api/{1}/user".format(provider_base_url, provider_api_version)
def complete_login(self, request, app, token, response):
response = requests.get(self.profile_url, params={"access_token": token.token})
data = _check_errors(response)
return self.get_provider().sociallogin_from_response(request, data)
oauth2_login = OAuth2LoginView.adapter_view(GitLabOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(GitLabOAuth2Adapter)