From e94ae561e097588320a02e011ed660f2fb25fde3 Mon Sep 17 00:00:00 2001 From: fufik Date: Wed, 24 Nov 2021 18:42:17 +0300 Subject: [PATCH] Add some basic functionality and fix broken Settings/Auth page --- auth_gitlab/apps.py | 2 - auth_gitlab/client.py | 3 +- auth_gitlab/constants.py | 10 +++ auth_gitlab/provider.py | 14 ++--- auth_gitlab/views.py | 129 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 140 insertions(+), 18 deletions(-) diff --git a/auth_gitlab/apps.py b/auth_gitlab/apps.py index b16a4db..4107489 100644 --- a/auth_gitlab/apps.py +++ b/auth_gitlab/apps.py @@ -5,7 +5,5 @@ class Config(AppConfig): def ready(self): from sentry.auth import register - from .provider import GitLabOAuth2Provider - register('gitlab', GitLabOAuth2Provider) diff --git a/auth_gitlab/client.py b/auth_gitlab/client.py index a19f415..2f9aaf8 100644 --- a/auth_gitlab/client.py +++ b/auth_gitlab/client.py @@ -5,7 +5,6 @@ from .constants import API_BASE_URL - class GitLabApiError(Exception): def __init__(self, message='', status=None): super().__init__(message) @@ -36,5 +35,5 @@ def _request(self, path): raise GitLabApiError(req.content, status=req.status_code) return json.loads(req.content) - def get_user(self, access_token): + def get_user(self): return self._request('user') diff --git a/auth_gitlab/constants.py b/auth_gitlab/constants.py index 0232c13..ef3044e 100644 --- a/auth_gitlab/constants.py +++ b/auth_gitlab/constants.py @@ -11,3 +11,13 @@ ACCESS_TOKEN_URL = '{0}://{1}/oauth/token'.format(SCHEME, BASE_DOMAIN) AUTHORIZE_URL = '{0}://{1}/oauth/authorize'.format(SCHEME, BASE_DOMAIN) API_BASE_URL = '{0}://{1}/api/v{2}'.format(SCHEME, BASE_DOMAIN, API_VERSION) + +# Just dummies from copied GitHub API so far +ERR_NO_ORG_ACCESS = "You do not have access to the required GitLab organization." +ERR_NO_PRIMARY_EMAIL = "We were unable to find a primary email address associated with your GitLab account." + +ERR_NO_SINGLE_PRIMARY_EMAIL = "We were unable to find a single primary email address associated with your GitLab account." + +ERR_NO_VERIFIED_PRIMARY_EMAIL = "We were unable to find a verified, primary email address associated with your GitLab account." + +ERR_NO_SINGLE_VERIFIED_PRIMARY_EMAIL = "We were unable to find a single verified, primary email address associated with your GitLab account" diff --git a/auth_gitlab/provider.py b/auth_gitlab/provider.py index 1a073a8..8539193 100644 --- a/auth_gitlab/provider.py +++ b/auth_gitlab/provider.py @@ -1,15 +1,9 @@ -from sentry.auth.providers.oauth2 import ( - OAuth2Callback, OAuth2Provider, OAuth2Login -) - -from .constants import ( - AUTHORIZE_URL, ACCESS_TOKEN_URL, CLIENT_ID, CLIENT_SECRET, SCOPE -) -from .views import FetchUser - +from sentry.auth.providers.oauth2 import OAuth2Callback, OAuth2Provider, OAuth2Login +from .constants import AUTHORIZE_URL, ACCESS_TOKEN_URL, CLIENT_ID, CLIENT_SECRET, SCOPE +from .views import ConfirmEmail, FetchUser, GitLabConfigureView, SelectOrganization class GitLabOAuth2Provider(OAuth2Provider): - name = 'Gitlab' + name = 'GitLab' client_id = CLIENT_ID client_secret = CLIENT_SECRET diff --git a/auth_gitlab/views.py b/auth_gitlab/views.py index c7d0a4a..81e62d3 100644 --- a/auth_gitlab/views.py +++ b/auth_gitlab/views.py @@ -1,13 +1,134 @@ -from __future__ import absolute_import +from django import forms -from sentry.auth.view import AuthView +from sentry.auth.view import AuthView, ConfigureView +from sentry.models import AuthIdentity from .client import GitLabClient +from .constants import ( + ERR_NO_ORG_ACCESS, + ERR_NO_PRIMARY_EMAIL, + ERR_NO_SINGLE_PRIMARY_EMAIL, + ERR_NO_SINGLE_VERIFIED_PRIMARY_EMAIL, + ERR_NO_VERIFIED_PRIMARY_EMAIL, +# REQUIRE_VERIFIED_EMAIL, +) + + +def _get_name_from_email(email): + """ + Given an email return a capitalized name. Ex. john.smith@example.com would return John Smith. + """ + name = email.rsplit("@", 1)[0] + name = " ".join(n_part.capitalize() for n_part in name.split(".")) + return name class FetchUser(AuthView): + def __init__(self, org=None, *args, **kwargs): + self.org = org + super().__init__(*args, **kwargs) + def handle(self, request, helper): - with GitHubClient(helper.fetch_state("data")["access_token"]) as client: + with GitLabClient(helper.fetch_state("data")["access_token"]) as client: + if self.org is not None: + if not client.is_org_member(self.org["id"]): + return helper.error(ERR_NO_ORG_ACCESS) + user = client.get_user() - helper.bind_state('user', user) + + #if not user.get("email"): + # emails = client.get_user_emails() + # email = [ + # e["email"] + # for e in emails + # if ((not REQUIRE_VERIFIED_EMAIL) or e["verified"]) and e["primary"] + # ] + # if len(email) == 0: + # if REQUIRE_VERIFIED_EMAIL: + # msg = ERR_NO_VERIFIED_PRIMARY_EMAIL + # else: + # msg = ERR_NO_PRIMARY_EMAIL + # return helper.error(msg) + # elif len(email) > 1: + # if REQUIRE_VERIFIED_EMAIL: + # msg = ERR_NO_SINGLE_VERIFIED_PRIMARY_EMAIL + # else: + # msg = ERR_NO_SINGLE_PRIMARY_EMAIL + # return helper.error(msg) + # else: + # user["email"] = email[0] + + ## A user hasn't set their name in their Github profile so it isn't + ## populated in the response + #if not user.get("name"): + # user["name"] = _get_name_from_email(user["email"]) + + helper.bind_state("user", user) + + return helper.next_step() + + +class ConfirmEmailForm(forms.Form): + email = forms.EmailField(label="Email") + + +class ConfirmEmail(AuthView): + def handle(self, request, helper): + user = helper.fetch_state("user") + + # TODO(dcramer): this isn't ideal, but our current flow doesnt really + # support this behavior; + try: + auth_identity = AuthIdentity.objects.select_related("user").get( + auth_provider=helper.provider_model, ident=user["id"] + ) + except AuthIdentity.DoesNotExist: + pass + else: + user["email"] = auth_identity.user.email + + if user.get("email"): return helper.next_step() + + form = ConfirmEmailForm(request.POST or None) + if form.is_valid(): + user["email"] = form.cleaned_data["email"] + helper.bind_state("user", user) + return helper.next_step() + + return self.respond("sentry_auth_github/enter-email.html", {"form": form}) + + +class SelectOrganizationForm(forms.Form): + org = forms.ChoiceField(label="Organization") + + def __init__(self, org_list, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields["org"].choices = [(o["id"], o["login"]) for o in org_list] + self.fields["org"].widget.choices = self.fields["org"].choices + + +class SelectOrganization(AuthView): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def handle(self, request, helper): + with GitLabClient(helper.fetch_state("data")["access_token"]) as client: + org_list = client.get_org_list() + + form = SelectOrganizationForm(org_list, request.POST or None) + if form.is_valid(): + org_id = form.cleaned_data["org"] + org = [o for o in org_list if org_id == str(o["id"])][0] + helper.bind_state("org", org) + return helper.next_step() + + return self.respond( + "sentry_auth_github/select-organization.html", {"form": form, "org_list": org_list} + ) + + +class GitLabConfigureView(ConfigureView): + def dispatch(self, request, organization, auth_provider): + return self.render("sentry_auth_github/configure.html")