From 4304d5f8dbd37b85a4bd5c8e5e6a1af863bcd1b1 Mon Sep 17 00:00:00 2001 From: Dinesh Date: Fri, 31 Jan 2020 22:49:53 +0530 Subject: [PATCH] auth: Add support for GitLab authentication. With some tweaks by tabbott to the documentation and comments. Fixes #13694. --- docs/development/authentication.md | 11 +++++++ .../images/landing-page/logos/gitlab-icon.png | Bin 0 -> 8905 bytes templates/zerver/config_error.html | 15 ++++++++- templates/zerver/gitlab-error.md | 12 +++++++ zerver/migrations/0269_gitlab_auth.py | 21 ++++++++++++ zerver/models.py | 2 +- zerver/tests/test_auth_backends.py | 26 +++++++++++++-- zerver/tests/test_docs.py | 30 ++++++++++++++++++ zerver/views/auth.py | 3 ++ zproject/backends.py | 19 +++++++++++ zproject/default_settings.py | 1 + zproject/dev_settings.py | 1 + zproject/prod_settings_template.py | 22 +++++++++++++ zproject/settings.py | 1 + zproject/test_settings.py | 2 ++ zproject/urls.py | 3 ++ 16 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 static/images/landing-page/logos/gitlab-icon.png create mode 100644 templates/zerver/gitlab-error.md create mode 100644 zerver/migrations/0269_gitlab_auth.py diff --git a/docs/development/authentication.md b/docs/development/authentication.md index 210fccfb94..21447181c0 100644 --- a/docs/development/authentication.md +++ b/docs/development/authentication.md @@ -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/). diff --git a/static/images/landing-page/logos/gitlab-icon.png b/static/images/landing-page/logos/gitlab-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..35cb5e9dab01596bc9363aac8b2a4110e3599eef GIT binary patch literal 8905 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 G#{U5a + For more information, have a look at + the authentication + setup guide for the development environment. +

+ {% 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 %}

For more information, have a look at diff --git a/templates/zerver/gitlab-error.md b/templates/zerver/gitlab-error.md new file mode 100644 index 0000000000..612bfb1800 --- /dev/null +++ b/templates/zerver/gitlab-error.md @@ -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. diff --git a/zerver/migrations/0269_gitlab_auth.py b/zerver/migrations/0269_gitlab_auth.py new file mode 100644 index 0000000000..d8b20bf82c --- /dev/null +++ b/zerver/migrations/0269_gitlab_auth.py @@ -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), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 247dc757c6..3a4cf67310 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -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 diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index 976f3bae47..a85a8ebe38 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -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 diff --git a/zerver/tests/test_docs.py b/zerver/tests/test_docs.py index 973aa7db91..60ec2b3f5b 100644 --- a/zerver/tests/test_docs.py +++ b/zerver/tests/test_docs.py @@ -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") diff --git a/zerver/views/auth.py b/zerver/views/auth.py index b48aeed4fe..81ce7c57fd 100644 --- a/zerver/views/auth.py +++ b/zerver/views/auth.py @@ -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) diff --git a/zproject/backends.py b/zproject/backends.py index ef5c53ac86..c50131a408 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -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 diff --git a/zproject/default_settings.py b/zproject/default_settings.py index fc133f957a..d33173e6a0 100644 --- a/zproject/default_settings.py +++ b/zproject/default_settings.py @@ -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) diff --git a/zproject/dev_settings.py b/zproject/dev_settings.py index 4083a51ba0..73a075129b 100644 --- a/zproject/dev_settings.py +++ b/zproject/dev_settings.py @@ -47,6 +47,7 @@ AUTHENTICATION_BACKENDS = ( 'zproject.backends.GoogleAuthBackend', 'zproject.backends.SAMLAuthBackend', # 'zproject.backends.AzureADAuthBackend', + 'zproject.backends.GitLabAuthBackend', ) EXTERNAL_URI_SCHEME = "http://" diff --git a/zproject/prod_settings_template.py b/zproject/prod_settings_template.py index b88381a888..0ac046e14a 100644 --- a/zproject/prod_settings_template.py +++ b/zproject/prod_settings_template.py @@ -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 = +####### +# 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 = +#SOCIAL_AUTH_GITLAB_API_URL = https://gitlab.example.com + ######## # GitHub OAuth. # diff --git a/zproject/settings.py b/zproject/settings.py index 4491512cf9..5316305ba0 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -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 diff --git a/zproject/test_settings.py b/zproject/test_settings.py index ff20599f9b..ab46fae1be 100644 --- a/zproject/test_settings.py +++ b/zproject/test_settings.py @@ -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' diff --git a/zproject/urls.py b/zproject/urls.py index c7ca542ae9..35922e2ba0 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -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},),