Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ Other Methods
'''''''''''''

A ``webserver_config.py`` configuration file is automatically generated and can be used to configure FAB auth manager to support authentication
methods like OAuth, OpenID, LDAP, REMOTE_USER. It should be noted that due to the limitation of Flask AppBuilder
and Authlib, only a selection of OAuth2 providers is supported. This list includes ``github``, ``githublocal``, ``twitter``,
``linkedin``, ``google``, ``azure``, ``openshift``, ``okta``, ``keycloak`` and ``keycloak_before_17``.
methods like OAuth, OpenID, LDAP and REMOTE_USER. It should be noted that due to the limitation of Flask AppBuilder
and Authlib, some OAuth2 providers may not be supported. Currently supported providers include ``github``, ``githublocal``, ``twitter``,
``linkedin``, ``google``, ``azure``, ``openshift``, ``okta``, ``auth0``, ``keycloak``, ``keycloak_before_17`` and ``authentik``.
If your provider is not on the list, you may need to adjust the ``remote_app`` configuration to match your provider's OAuth2 specification.

By default, the following entry in the ``$AIRFLOW_HOME/webserver_config.py`` is used.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2132,8 +2132,16 @@ def get_oauth_user_info(self, provider: str, resp: dict[str, Any]) -> dict[str,
"username": me["nickname"],
"role_keys": me.get("groups", []),
}

return {}
# for other providers
data = self.oauth_remotes[provider].userinfo()
log.debug("User info from %s: %s", provider, data)
return {
"username": data.get("preferred_username", ""),
"first_name": data.get("given_name", ""),
"last_name": data.get("family_name", ""),
"email": data.get("email", ""),
"role_keys": data.get("groups", []),
}

@staticmethod
def oauth_token_getter():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from unittest import mock
from unittest.mock import Mock

import pytest

from tests_common.test_utils.compat import ignore_provider_compatibility_error

with ignore_provider_compatibility_error("2.9.0+", __file__):
Expand Down Expand Up @@ -75,3 +77,173 @@ def test_check_password_not_match(self, check_password):
sm.find_user = Mock(return_value=mock_user)
check_password.return_value = False
assert not sm.check_password("test_user", "test_password")

@pytest.mark.parametrize(
"provider, resp, user_info",
[
("github", {"login": "test"}, {"username": "github_test"}),
("githublocal", {"login": "test"}, {"username": "github_test"}),
("twitter", {"screen_name": "test"}, {"username": "twitter_test"}),
(
"linkedin",
{"id": "test", "firstName": "John", "lastName": "Doe", "email-address": "test@example.com"},
{
"username": "linkedin_test",
"first_name": "John",
"last_name": "Doe",
"email": "test@example.com",
},
),
(
"google",
{"id": "test", "given_name": "John", "family_name": "Doe", "email": "test@example.com"},
{
"username": "google_test",
"first_name": "John",
"last_name": "Doe",
"email": "test@example.com",
},
),
(
"azure",
{
"oid": "test",
"given_name": "John",
"family_name": "Doe",
"email": "test@example.com",
"roles": ["admin"],
},
{
"username": "test",
"first_name": "John",
"last_name": "Doe",
"email": "test@example.com",
"role_keys": ["admin"],
},
),
(
"azure",
{
"oid": "test",
"given_name": "John",
"family_name": "Doe",
"upn": "test@example.com",
"roles": ["admin"],
},
{
"username": "test",
"first_name": "John",
"last_name": "Doe",
"email": "test@example.com",
"role_keys": ["admin"],
},
),
("openshift", {"metadata": {"name": "test"}}, {"username": "openshift_test"}),
(
"okta",
{
"sub": "test",
"given_name": "John",
"family_name": "Doe",
"email": "test@example.com",
"groups": ["admin"],
},
{
"username": "okta_test",
"first_name": "John",
"last_name": "Doe",
"email": "test@example.com",
"role_keys": ["admin"],
},
),
("okta", {"error": "access_denied", "error_description": "Invalid bearer token."}, {}),
(
"auth0",
{
"sub": "test",
"given_name": "John",
"family_name": "Doe",
"email": "test@example.com",
"groups": ["admin"],
},
{
"username": "auth0_test",
"first_name": "John",
"last_name": "Doe",
"email": "test@example.com",
"role_keys": ["admin"],
},
),
(
"keycloak",
{
"preferred_username": "test",
"given_name": "John",
"family_name": "Doe",
"email": "test@example.com",
"groups": ["admin"],
},
{
"username": "test",
"first_name": "John",
"last_name": "Doe",
"email": "test@example.com",
"role_keys": ["admin"],
},
),
(
"keycloak_before_17",
{
"preferred_username": "test",
"given_name": "John",
"family_name": "Doe",
"email": "test@example.com",
"groups": ["admin"],
},
{
"username": "test",
"first_name": "John",
"last_name": "Doe",
"email": "test@example.com",
"role_keys": ["admin"],
},
),
(
"authentik",
{
"nickname": "test",
"given_name": "John",
"preferred_username": "test@example.com",
"groups": ["admin"],
},
{
"username": "test",
"first_name": "John",
"email": "test@example.com",
"role_keys": ["admin"],
},
),
(
"other",
{"preferred_username": "test", "email": "test@example.com"},
{
"username": "test",
"first_name": "",
"last_name": "",
"email": "test@example.com",
"role_keys": [],
},
),
],
)
def test_get_oauth_user_info(self, provider, resp, user_info):
sm = EmptySecurityManager()
sm.appbuilder = Mock(sm=sm)
sm.oauth_remotes = {}
sm.oauth_remotes[provider] = Mock(
get=Mock(return_value=Mock(json=Mock(return_value=resp))),
userinfo=Mock(return_value=resp),
)
sm._decode_and_validate_azure_jwt = Mock(return_value=resp)
sm._get_authentik_token_info = Mock(return_value=resp)
assert sm.get_oauth_user_info(provider, {"id_token": None}) == user_info