From 422b0758862cab67207201535e0d60d6434bef9f Mon Sep 17 00:00:00 2001 From: WAJAHME Date: Sun, 30 Jul 2023 16:12:45 +0200 Subject: [PATCH 1/4] github enterprise server has a different API endpoint vs github free, pro, team --- docs/auth.rst | 2 ++ flower/views/auth.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/auth.rst b/docs/auth.rst index 8227b6453..175bb1b68 100644 --- a/docs/auth.rst +++ b/docs/auth.rst @@ -86,6 +86,8 @@ Here's an example configuration file with the Github OAuth options: Replace `` and `` with the actual Client ID and secret obtained from the Github Settings. +If using Github Enterprise, you can set the `FLOWER_GITHUB_OAUTH_DOMAIN` environment variable to the base URL of your Github Enterprise instance. + See `GitHub OAuth API`_ docs for more info. .. _Github Settings: https://github.com/settings/applications/new diff --git a/flower/views/auth.py b/flower/views/auth.py index b93ad185b..34a342306 100644 --- a/flower/views/auth.py +++ b/flower/views/auth.py @@ -90,6 +90,10 @@ class GithubLoginHandler(BaseHandler, tornado.auth.OAuth2Mixin): _OAUTH_DOMAIN = os.getenv( "FLOWER_GITHUB_OAUTH_DOMAIN", "github.com") + if _OAUTH_DOMAIN == "github.com": + _OAUTH_API_URL = f'https://api.{_OAUTH_DOMAIN}/user/emails' + else: + _OAUTH_API_URL = f'https://{_OAUTH_DOMAIN}/api/v3/user/emails' _OAUTH_AUTHORIZE_URL = f'https://{_OAUTH_DOMAIN}/login/oauth/authorize' _OAUTH_ACCESS_TOKEN_URL = f'https://{_OAUTH_DOMAIN}/login/oauth/access_token' _OAUTH_NO_CALLBACKS = False @@ -138,7 +142,7 @@ async def _on_auth(self, user): access_token = user['access_token'] response = await self.get_auth_http_client().fetch( - f'https://api.{self._OAUTH_DOMAIN}/user/emails', + self._OAUTH_API_URL, headers={'Authorization': 'token ' + access_token, 'User-agent': 'Tornado auth'}) From 5a86d29217e6ab75b853c56d2444e71197ec291e Mon Sep 17 00:00:00 2001 From: lucifer-5821 Date: Mon, 2 Feb 2026 17:18:07 +0100 Subject: [PATCH 2/4] Update flower/views/auth.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- flower/views/auth.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flower/views/auth.py b/flower/views/auth.py index 34a342306..788143c3e 100644 --- a/flower/views/auth.py +++ b/flower/views/auth.py @@ -92,10 +92,12 @@ class GithubLoginHandler(BaseHandler, tornado.auth.OAuth2Mixin): "FLOWER_GITHUB_OAUTH_DOMAIN", "github.com") if _OAUTH_DOMAIN == "github.com": _OAUTH_API_URL = f'https://api.{_OAUTH_DOMAIN}/user/emails' + _OAUTH_AUTHORIZE_URL = f'https://{_OAUTH_DOMAIN}/login/oauth/authorize' + _OAUTH_ACCESS_TOKEN_URL = f'https://{_OAUTH_DOMAIN}/login/oauth/access_token' else: _OAUTH_API_URL = f'https://{_OAUTH_DOMAIN}/api/v3/user/emails' - _OAUTH_AUTHORIZE_URL = f'https://{_OAUTH_DOMAIN}/login/oauth/authorize' - _OAUTH_ACCESS_TOKEN_URL = f'https://{_OAUTH_DOMAIN}/login/oauth/access_token' + _OAUTH_AUTHORIZE_URL = f'https://{_OAUTH_DOMAIN}/oauth/authorize' + _OAUTH_ACCESS_TOKEN_URL = f'https://{_OAUTH_DOMAIN}/oauth/access_token' _OAUTH_NO_CALLBACKS = False _OAUTH_SETTINGS_KEY = 'oauth' From 363f3191ae8e467f85b254d298288b3700c61b8d Mon Sep 17 00:00:00 2001 From: ahmed-wajid Date: Sun, 5 Apr 2026 15:20:27 +0200 Subject: [PATCH 3/4] Add tests for OAuth handlers --- tests/unit/views/test_auth.py | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/unit/views/test_auth.py b/tests/unit/views/test_auth.py index 941ed4aa1..871e9042f 100644 --- a/tests/unit/views/test_auth.py +++ b/tests/unit/views/test_auth.py @@ -1,4 +1,6 @@ -from flower.views.auth import authenticate, validate_auth_option +import importlib +import os +from flower.views.auth import authenticate, validate_auth_option, GithubLoginHandler, GitLabLoginHandler from tests.unit import AsyncHTTPTestCase @@ -60,3 +62,36 @@ def test_authenticate_wildcard_email(self): self.assertFalse(authenticate(".*@example.com", "attacker@example.com.attacker.com")) self.assertFalse(authenticate(".*@corp.example.com", "attacker@corpZexample.com")) self.assertFalse(authenticate(".*@corp\.example\.com", "attacker@corpZexample.com")) + + +class OAuthTests(AsyncHTTPTestCase): + def test_github_oauth_urls_default(self): + # Test default GitHub.com URLs + self.assertEqual(GithubLoginHandler._OAUTH_DOMAIN, 'github.com') + self.assertEqual(GithubLoginHandler._OAUTH_API_URL, 'https://api.github.com/user/emails') + self.assertEqual(GithubLoginHandler._OAUTH_AUTHORIZE_URL, 'https://github.com/login/oauth/authorize') + self.assertEqual(GithubLoginHandler._OAUTH_ACCESS_TOKEN_URL, 'https://github.com/login/oauth/access_token') + + def test_github_oauth_urls_enterprise(self): + # Test GitHub Enterprise URLs by reloading the module with env set + import flower.views.auth as auth_module + original_env = os.environ.get('FLOWER_GITHUB_OAUTH_DOMAIN') + try: + os.environ['FLOWER_GITHUB_OAUTH_DOMAIN'] = 'github.example.com' + importlib.reload(auth_module) + self.assertEqual(auth_module.GithubLoginHandler._OAUTH_DOMAIN, 'github.example.com') + self.assertEqual(auth_module.GithubLoginHandler._OAUTH_API_URL, 'https://github.example.com/api/v3/user/emails') + self.assertEqual(auth_module.GithubLoginHandler._OAUTH_AUTHORIZE_URL, 'https://github.example.com/oauth/authorize') + self.assertEqual(auth_module.GithubLoginHandler._OAUTH_ACCESS_TOKEN_URL, 'https://github.example.com/oauth/access_token') + finally: + if original_env is None: + os.environ.pop('FLOWER_GITHUB_OAUTH_DOMAIN', None) + else: + os.environ['FLOWER_GITHUB_OAUTH_DOMAIN'] = original_env + importlib.reload(auth_module) # reset to default + + def test_gitlab_oauth_urls_default(self): + # Test default GitLab.com URLs + self.assertEqual(GitLabLoginHandler._OAUTH_GITLAB_DOMAIN, 'gitlab.com') + self.assertEqual(GitLabLoginHandler._OAUTH_AUTHORIZE_URL, 'https://gitlab.com/oauth/authorize') + self.assertEqual(GitLabLoginHandler._OAUTH_ACCESS_TOKEN_URL, 'https://gitlab.com/oauth/token') From 245105801d3a9df7da52939e948ea06869d27406 Mon Sep 17 00:00:00 2001 From: ahmed-wajid Date: Sun, 5 Apr 2026 15:37:02 +0200 Subject: [PATCH 4/4] Move GitHub OAuth URL logic into initialize and add helper tests --- flower/views/auth.py | 38 +++++++++++++++++++++-------- tests/unit/views/test_auth.py | 45 ++++++++++++----------------------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/flower/views/auth.py b/flower/views/auth.py index 788143c3e..fb00c1072 100644 --- a/flower/views/auth.py +++ b/flower/views/auth.py @@ -88,19 +88,37 @@ def __new__(cls, *args, **kwargs): class GithubLoginHandler(BaseHandler, tornado.auth.OAuth2Mixin): - _OAUTH_DOMAIN = os.getenv( - "FLOWER_GITHUB_OAUTH_DOMAIN", "github.com") - if _OAUTH_DOMAIN == "github.com": - _OAUTH_API_URL = f'https://api.{_OAUTH_DOMAIN}/user/emails' - _OAUTH_AUTHORIZE_URL = f'https://{_OAUTH_DOMAIN}/login/oauth/authorize' - _OAUTH_ACCESS_TOKEN_URL = f'https://{_OAUTH_DOMAIN}/login/oauth/access_token' - else: - _OAUTH_API_URL = f'https://{_OAUTH_DOMAIN}/api/v3/user/emails' - _OAUTH_AUTHORIZE_URL = f'https://{_OAUTH_DOMAIN}/oauth/authorize' - _OAUTH_ACCESS_TOKEN_URL = f'https://{_OAUTH_DOMAIN}/oauth/access_token' + _OAUTH_DOMAIN = "github.com" + _OAUTH_API_URL = 'https://api.github.com/user/emails' + _OAUTH_AUTHORIZE_URL = 'https://github.com/login/oauth/authorize' + _OAUTH_ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token' _OAUTH_NO_CALLBACKS = False _OAUTH_SETTINGS_KEY = 'oauth' + @classmethod + def _get_oauth_urls(cls, oauth_domain): + if oauth_domain == 'github.com': + return ( + f'https://api.{oauth_domain}/user/emails', + f'https://{oauth_domain}/login/oauth/authorize', + f'https://{oauth_domain}/login/oauth/access_token', + ) + return ( + f'https://{oauth_domain}/api/v3/user/emails', + f'https://{oauth_domain}/oauth/authorize', + f'https://{oauth_domain}/oauth/access_token', + ) + + def initialize(self, *args, **kwargs): + super().initialize(*args, **kwargs) + oauth_domain = os.getenv('FLOWER_GITHUB_OAUTH_DOMAIN', 'github.com') + ( + self._OAUTH_API_URL, + self._OAUTH_AUTHORIZE_URL, + self._OAUTH_ACCESS_TOKEN_URL, + ) = self._get_oauth_urls(oauth_domain) + self._OAUTH_DOMAIN = oauth_domain + async def get_authenticated_user(self, redirect_uri, code): body = urlencode({ "redirect_uri": redirect_uri, diff --git a/tests/unit/views/test_auth.py b/tests/unit/views/test_auth.py index 871e9042f..4562dfff0 100644 --- a/tests/unit/views/test_auth.py +++ b/tests/unit/views/test_auth.py @@ -1,6 +1,4 @@ -import importlib -import os -from flower.views.auth import authenticate, validate_auth_option, GithubLoginHandler, GitLabLoginHandler +from flower.views.auth import authenticate, validate_auth_option, GithubLoginHandler from tests.unit import AsyncHTTPTestCase @@ -61,37 +59,24 @@ def test_authenticate_wildcard_email(self): self.assertTrue(authenticate("one.*@example.com", "one.two@example.com")) self.assertFalse(authenticate(".*@example.com", "attacker@example.com.attacker.com")) self.assertFalse(authenticate(".*@corp.example.com", "attacker@corpZexample.com")) - self.assertFalse(authenticate(".*@corp\.example\.com", "attacker@corpZexample.com")) + self.assertFalse(authenticate(r".*@corp\.example\.com", "attacker@corpZexample.com")) class OAuthTests(AsyncHTTPTestCase): - def test_github_oauth_urls_default(self): - # Test default GitHub.com URLs + def test_get_oauth_urls_for_github_com(self): + api_url, authorize_url, access_token_url = GithubLoginHandler._get_oauth_urls('github.com') + self.assertEqual(api_url, 'https://api.github.com/user/emails') + self.assertEqual(authorize_url, 'https://github.com/login/oauth/authorize') + self.assertEqual(access_token_url, 'https://github.com/login/oauth/access_token') + + def test_get_oauth_urls_for_github_enterprise(self): + api_url, authorize_url, access_token_url = GithubLoginHandler._get_oauth_urls('github.example.com') + self.assertEqual(api_url, 'https://github.example.com/api/v3/user/emails') + self.assertEqual(authorize_url, 'https://github.example.com/oauth/authorize') + self.assertEqual(access_token_url, 'https://github.example.com/oauth/access_token') + + def test_github_login_handler_defaults_remain_github(self): self.assertEqual(GithubLoginHandler._OAUTH_DOMAIN, 'github.com') self.assertEqual(GithubLoginHandler._OAUTH_API_URL, 'https://api.github.com/user/emails') self.assertEqual(GithubLoginHandler._OAUTH_AUTHORIZE_URL, 'https://github.com/login/oauth/authorize') self.assertEqual(GithubLoginHandler._OAUTH_ACCESS_TOKEN_URL, 'https://github.com/login/oauth/access_token') - - def test_github_oauth_urls_enterprise(self): - # Test GitHub Enterprise URLs by reloading the module with env set - import flower.views.auth as auth_module - original_env = os.environ.get('FLOWER_GITHUB_OAUTH_DOMAIN') - try: - os.environ['FLOWER_GITHUB_OAUTH_DOMAIN'] = 'github.example.com' - importlib.reload(auth_module) - self.assertEqual(auth_module.GithubLoginHandler._OAUTH_DOMAIN, 'github.example.com') - self.assertEqual(auth_module.GithubLoginHandler._OAUTH_API_URL, 'https://github.example.com/api/v3/user/emails') - self.assertEqual(auth_module.GithubLoginHandler._OAUTH_AUTHORIZE_URL, 'https://github.example.com/oauth/authorize') - self.assertEqual(auth_module.GithubLoginHandler._OAUTH_ACCESS_TOKEN_URL, 'https://github.example.com/oauth/access_token') - finally: - if original_env is None: - os.environ.pop('FLOWER_GITHUB_OAUTH_DOMAIN', None) - else: - os.environ['FLOWER_GITHUB_OAUTH_DOMAIN'] = original_env - importlib.reload(auth_module) # reset to default - - def test_gitlab_oauth_urls_default(self): - # Test default GitLab.com URLs - self.assertEqual(GitLabLoginHandler._OAUTH_GITLAB_DOMAIN, 'gitlab.com') - self.assertEqual(GitLabLoginHandler._OAUTH_AUTHORIZE_URL, 'https://gitlab.com/oauth/authorize') - self.assertEqual(GitLabLoginHandler._OAUTH_ACCESS_TOKEN_URL, 'https://gitlab.com/oauth/token')