auth: Add support for GitLab authentication.

With some tweaks by tabbott to the documentation and comments.

Fixes #13694.
This commit is contained in:
Dinesh 2020-01-31 22:49:53 +05:30 committed by Tim Abbott
parent 4cb94bf483
commit 4304d5f8db
16 changed files with 164 additions and 5 deletions

View File

@ -72,6 +72,17 @@ details worth understanding:
`social_auth_github_key` to the client ID and `social_auth_github_secret`
to the client secret.
### GitLab
* Register an OAuth application with GitLab at
https://gitlab.com/oauth/applications.
Specify `http://zulipdev.com:9991/complete/gitlab` as the callback URL.
* You should get a page containing the Application ID and Secret for
your new application. In `dev-secrets.conf`, enter the Application
ID as `social_auth_gitlab_key` and the Secret as
`social_auth_gitlab_secret`.
### SAML
* Sign up for a [developer Okta account](https://developer.okta.com/).

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -74,7 +74,20 @@
{% endif %}
{% endif %}
{% if google_error or github_error %}
{% if gitlab_error %}
{% if development_environment %}
{{ render_markdown_path('zerver/gitlab-error.md', {"root_domain_uri": root_domain_uri, "settings_path": secrets_path, "secrets_path": secrets_path, "client_id_key_name": "social_auth_gitlab_key"}) }}
<p>
For more information, have a look at
the <a href="https://zulip.readthedocs.io/en/latest/development/authentication.html#gitlab">authentication
setup guide</a> for the development environment.
</p>
{% else %}
{{ render_markdown_path('zerver/gitlab-error.md', {"root_domain_uri": root_domain_uri, "settings_path": settings_path, "secrets_path": secrets_path, "client_id_key_name": "SOCIAL_AUTH_GITLAB_KEY"}) }}
{% endif %}
{% endif %}
{% if google_error or github_error or gitlab_error %}
{% if not development_environment %}
<p>
For more information, have a look at

View File

@ -0,0 +1,12 @@
You are using the **GitLab auth backend**, but it is not properly
configured. Please check the following:
* You have added `{{ root_domain_uri }}/complete/gitlab/` as the callback
URL in the OAuth application in GitLab. You can register OAuth apps at
[GitLab Applications](https://gitlab.com/profile/applications).
* You have set `{{ client_id_key_name }}` in `{{ settings_path }}` and
`social_auth_gitlab_secret` in `{{ secrets_path }}` with the values
from your OAuth application.
* Navigate back to the login page and attempt the GitLab auth flow again.

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.26 on 2020-01-29 17:30
from __future__ import unicode_literals
import bitfield.models
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('zerver', '0268_add_userpresence_realm_timestamp_index'),
]
operations = [
migrations.AlterField(
model_name='realm',
name='authentication_methods',
field=bitfield.models.BitField(['Google', 'Email', 'GitHub', 'LDAP', 'Dev', 'RemoteUser', 'AzureAD', 'SAML', 'GitLab'], default=2147483647),
),
]

View File

@ -136,7 +136,7 @@ class Realm(models.Model):
INVITES_STANDARD_REALM_DAILY_MAX = 3000
MESSAGE_VISIBILITY_LIMITED = 10000
AUTHENTICATION_FLAGS = [u'Google', u'Email', u'GitHub', u'LDAP', u'Dev',
u'RemoteUser', u'AzureAD', u'SAML']
u'RemoteUser', u'AzureAD', u'SAML', u'GitLab']
SUBDOMAIN_FOR_ROOT_DOMAIN = ''
# User-visible display name and description used on e.g. the organization homepage

View File

@ -58,9 +58,9 @@ from confirmation.models import Confirmation, create_confirmation_link
from zproject.backends import ZulipDummyBackend, EmailAuthBackend, \
GoogleAuthBackend, ZulipRemoteUserBackend, ZulipLDAPAuthBackend, \
ZulipLDAPUserPopulator, DevAuthBackend, GitHubAuthBackend, ZulipAuthMixin, \
dev_auth_enabled, password_auth_enabled, github_auth_enabled, google_auth_enabled, \
require_email_format_usernames, AUTH_BACKEND_NAME_MAP, \
ZulipLDAPUserPopulator, DevAuthBackend, GitHubAuthBackend, GitLabAuthBackend, ZulipAuthMixin, \
dev_auth_enabled, password_auth_enabled, github_auth_enabled, gitlab_auth_enabled, \
google_auth_enabled, require_email_format_usernames, AUTH_BACKEND_NAME_MAP, \
ZulipLDAPConfigurationError, ZulipLDAPExceptionNoMatchingLDAPUser, ZulipLDAPExceptionOutsideDomain, \
ZulipLDAPException, query_ldap, sync_user_from_ldap, SocialAuthMixin, \
PopulateUserLDAPError, SAMLAuthBackend, saml_auth_enabled, email_belongs_to_ldap, \
@ -1695,6 +1695,26 @@ class GitHubAuthBackendTest(SocialAuthBase):
mock_warning.assert_called_once_with("Social auth (GitHub) failed because user has no verified"
" emails associated with the account")
class GitLabAuthBackendTest(SocialAuthBase):
__unittest_skip__ = False
BACKEND_CLASS = GitLabAuthBackend
CLIENT_KEY_SETTING = "SOCIAL_AUTH_GITLAB_KEY"
LOGIN_URL = "/accounts/login/social/gitlab"
SIGNUP_URL = "/accounts/register/social/gitlab"
AUTHORIZATION_URL = "https://gitlab.com/oauth/authorize"
ACCESS_TOKEN_URL = "https://gitlab.com/oauth/token"
USER_INFO_URL = "https://gitlab.com/api/v4/user"
AUTH_FINISH_URL = "/complete/gitlab/"
CONFIG_ERROR_URL = "/config-error/gitlab"
def test_gitlab_auth_enabled(self) -> None:
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitLabAuthBackend',)):
self.assertTrue(gitlab_auth_enabled())
def get_account_data_dict(self, email: str, name: str) -> Dict[str, Any]:
return dict(email=email, name=name, email_verified=True)
class GoogleAuthBackendTest(SocialAuthBase):
__unittest_skip__ = False

View File

@ -413,6 +413,36 @@ class ConfigErrorTest(ZulipTestCase):
self.assert_not_in_success_response(["zproject/dev_settings.py"], result)
self.assert_not_in_success_response(["zproject/dev-secrets.conf"], result)
@override_settings(SOCIAL_AUTH_GITLAB_KEY=None)
def test_gitlab(self) -> None:
result = self.client_get("/accounts/login/social/gitlab")
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, '/config-error/gitlab')
result = self.client_get(result.url)
self.assert_in_success_response(["social_auth_gitlab_key"], result)
self.assert_in_success_response(["social_auth_gitlab_secret"], result)
self.assert_in_success_response(["zproject/dev-secrets.conf"], result)
self.assert_not_in_success_response(["SOCIAL_AUTH_GITLAB_KEY"], result)
self.assert_not_in_success_response(["zproject/dev_settings.py"], result)
self.assert_not_in_success_response(["/etc/zulip/settings.py"], result)
self.assert_not_in_success_response(["/etc/zulip/zulip-secrets.conf"], result)
@override_settings(SOCIAL_AUTH_GITLAB_KEY=None)
@override_settings(DEVELOPMENT=False)
def test_gitlab_production_error(self) -> None:
"""Test the !DEVELOPMENT code path of config-error."""
result = self.client_get("/accounts/login/social/gitlab")
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, '/config-error/gitlab')
result = self.client_get(result.url)
self.assert_in_success_response(["SOCIAL_AUTH_GITLAB_KEY"], result)
self.assert_in_success_response(["/etc/zulip/settings.py"], result)
self.assert_in_success_response(["social_auth_gitlab_secret"], result)
self.assert_in_success_response(["/etc/zulip/zulip-secrets.conf"], result)
self.assert_not_in_success_response(["social_auth_gitlab_key"], result)
self.assert_not_in_success_response(["zproject/dev_settings.py"], result)
self.assert_not_in_success_response(["zproject/dev-secrets.conf"], result)
@override_settings(SOCIAL_AUTH_SAML_ENABLED_IDPS=None)
def test_saml_error(self) -> None:
result = self.client_get("/accounts/login/social/saml")

View File

@ -429,6 +429,9 @@ def start_social_login(request: HttpRequest, backend: str, extra_arg: Optional[s
if (backend == "google") and not (settings.SOCIAL_AUTH_GOOGLE_KEY and
settings.SOCIAL_AUTH_GOOGLE_SECRET):
return redirect_to_config_error("google")
if (backend == "gitlab") and not (settings.SOCIAL_AUTH_GITLAB_KEY and
settings.SOCIAL_AUTH_GITLAB_SECRET):
return redirect_to_config_error("gitlab")
# TODO: Add a similar block for AzureAD.
return oauth_redirect_to_root(request, backend_url, 'social', extra_url_params=extra_url_params)

View File

@ -39,6 +39,7 @@ from onelogin.saml2.errors import OneLogin_Saml2_Error
from social_core.backends.github import GithubOAuth2, GithubOrganizationOAuth2, \
GithubTeamOAuth2
from social_core.backends.azuread import AzureADOAuth2
from social_core.backends.gitlab import GitLabOAuth2
from social_core.backends.base import BaseAuth
from social_core.backends.google import GoogleOAuth2
from social_core.backends.saml import SAMLAuth
@ -110,6 +111,9 @@ def google_auth_enabled(realm: Optional[Realm]=None) -> bool:
def github_auth_enabled(realm: Optional[Realm]=None) -> bool:
return auth_enabled_helper(['GitHub'], realm)
def gitlab_auth_enabled(realm: Optional[Realm]=None) -> bool:
return auth_enabled_helper(['GitLab'], realm)
def saml_auth_enabled(realm: Optional[Realm]=None) -> bool:
return auth_enabled_helper(['SAML'], realm)
@ -1324,6 +1328,21 @@ class AzureADAuthBackend(SocialAuthMixin, AzureADOAuth2):
auth_backend_name = "AzureAD"
display_icon = "/static/images/landing-page/logos/azuread-icon.png"
@external_auth_method
class GitLabAuthBackend(SocialAuthMixin, GitLabOAuth2):
sort_order = 75
name = "gitlab"
auth_backend_name = "GitLab"
display_icon = "/static/images/landing-page/logos/gitlab-icon.png"
# Note: GitLab as of early 2020 supports having multiple email
# addresses connected with a GitLab account, and we could access
# those emails, but its APIs don't indicate which of those email
# addresses were verified, so we cannot use them for
# authentication like we do for the GitHub integration. Instead,
# we just use the primary email address, which is always verified.
# (No code is required to do so, as that's the default behavior).
@external_auth_method
class GoogleAuthBackend(SocialAuthMixin, GoogleOAuth2):
sort_order = 150

View File

@ -50,6 +50,7 @@ FAKE_LDAP_NUM_USERS = 8
SOCIAL_AUTH_GITHUB_KEY = get_secret('social_auth_github_key', development_only=True)
SOCIAL_AUTH_GITHUB_ORG_NAME = None # type: Optional[str]
SOCIAL_AUTH_GITHUB_TEAM_ID = None # type: Optional[str]
SOCIAL_AUTH_GITLAB_KEY = get_secret('social_auth_gitlab_key')
SOCIAL_AUTH_SUBDOMAIN = None # type: Optional[str]
SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET = get_secret('azure_oauth2_secret')
SOCIAL_AUTH_GOOGLE_KEY = get_secret('social_auth_google_key', development_only=True)

View File

@ -47,6 +47,7 @@ AUTHENTICATION_BACKENDS = (
'zproject.backends.GoogleAuthBackend',
'zproject.backends.SAMLAuthBackend',
# 'zproject.backends.AzureADAuthBackend',
'zproject.backends.GitLabAuthBackend',
)
EXTERNAL_URI_SCHEME = "http://"

View File

@ -119,6 +119,7 @@ AUTHENTICATION_BACKENDS = (
'zproject.backends.EmailAuthBackend', # Email and password; just requires SMTP setup
# 'zproject.backends.GoogleAuthBackend', # Google auth, setup below
# 'zproject.backends.GitHubAuthBackend', # GitHub auth, setup below
# 'zproject.backends.GitLabAuthBackend', # GitLab auth, setup below
# 'zproject.backends.AzureADAuthBackend', # Microsoft Azure Active Directory auth, setup below
# 'zproject.backends.SAMLAuthBackend', # SAML, setup below
# 'zproject.backends.ZulipLDAPAuthBackend', # LDAP, setup below
@ -148,6 +149,27 @@ AUTHENTICATION_BACKENDS = (
# client secret in zulip-secrets.conf as `social_auth_google_secret`.
#SOCIAL_AUTH_GOOGLE_KEY = <your client ID from Google>
#######
# GitLab OAuth.
#
# To set up GitLab authentication, you'll need to do the following:
#
# (1) Register an OAuth application with GitLab at
# https://gitlab.com/oauth/applications
# Or the equivalent URL on a self-hosted GitLab server.
# (2) Fill in the "Redirect URI" with a value like
# http://zulip.example.com/complete/gitlab/
# based on your value for EXTERNAL_HOST.
# (3) For "scopes", select only "read_user", and create the application.
# (4) You'll end up on a page with the Application ID and Secret for
# your new GitLab Application. Use the Application ID as
# `SOCIAL_AUTH_GITLAB_KEY` here, and put the Secret in
# zulip-secrets.conf as `social_auth_gitlab_secret`.
# (5) If you are self-hosting GitLab, provide the URL of the
# GitLab server as SOCIAL_AUTH_GITLAB_API_URL here.
#SOCIAL_AUTH_GITLAB_KEY = <your Application ID from GitLab>
#SOCIAL_AUTH_GITLAB_API_URL = https://gitlab.example.com
########
# GitHub OAuth.
#

View File

@ -982,6 +982,7 @@ SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['subdomain', 'is_signup', 'mobile_flow_o
SOCIAL_AUTH_LOGIN_ERROR_URL = '/login/'
SOCIAL_AUTH_GITHUB_SECRET = get_secret('social_auth_github_secret')
SOCIAL_AUTH_GITLAB_SECRET = get_secret('social_auth_gitlab_secret')
SOCIAL_AUTH_GITHUB_SCOPE = ['user:email']
SOCIAL_AUTH_GITHUB_ORG_KEY = SOCIAL_AUTH_GITHUB_KEY
SOCIAL_AUTH_GITHUB_ORG_SECRET = SOCIAL_AUTH_GITHUB_SECRET

View File

@ -164,6 +164,8 @@ GOOGLE_OAUTH2_CLIENT_SECRET = "secret"
SOCIAL_AUTH_GITHUB_KEY = "key"
SOCIAL_AUTH_GITHUB_SECRET = "secret"
SOCIAL_AUTH_GITLAB_KEY = "key"
SOCIAL_AUTH_GITLAB_SECRET = "secret"
SOCIAL_AUTH_GOOGLE_KEY = "key"
SOCIAL_AUTH_GOOGLE_SECRET = "secret"
SOCIAL_AUTH_SUBDOMAIN = 'www'

View File

@ -572,6 +572,9 @@ i18n_urls = [
url(r'^config-error/github$', TemplateView.as_view(
template_name='zerver/config_error.html',),
{'github_error': True},),
url(r'^config-error/gitlab$', TemplateView.as_view(
template_name='zerver/config_error.html',),
{'gitlab_error': True},),
url(r'^config-error/smtp$', TemplateView.as_view(
template_name='zerver/config_error.html',),
{'smtp_error': True},),