Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Rest API auth related methods to FAB auth manager #34924

Merged
merged 9 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
56 changes: 19 additions & 37 deletions airflow/api/auth/backend/basic_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,56 +14,38 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Basic authentication backend."""
from __future__ import annotations
"""
This module is deprecated.

from functools import wraps
from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
Please use :mod:`airflow.auth.managers.fab.api.auth.backend.basic_auth` instead.
"""
from __future__ import annotations

from flask import Response, request
from flask_appbuilder.const import AUTH_LDAP
from flask_login import login_user
import warnings
from typing import TYPE_CHECKING, Any, Callable

from airflow.utils.airflow_flask_app import get_airflow_app
import airflow.auth.managers.fab.api.auth.backend.basic_auth as fab_basic_auth
from airflow.exceptions import RemovedInAirflow3Warning

if TYPE_CHECKING:
from airflow.auth.managers.fab.models import User

CLIENT_AUTH: tuple[str, str] | Any | None = None


def init_app(_):
"""Initialize authentication backend."""
warnings.warn(
"This module is deprecated. Please use `airflow.auth.managers.fab.api.auth.backend.basic_auth` instead.",
RemovedInAirflow3Warning,
stacklevel=2,
)


T = TypeVar("T", bound=Callable)
def init_app(_):
fab_basic_auth.init_app(_)


def auth_current_user() -> User | None:
"""Authenticate and set current user if Authorization header exists."""
auth = request.authorization
if auth is None or not auth.username or not auth.password:
return None

ab_security_manager = get_airflow_app().appbuilder.sm
user = None
if ab_security_manager.auth_type == AUTH_LDAP:
user = ab_security_manager.auth_user_ldap(auth.username, auth.password)
if user is None:
user = ab_security_manager.auth_user_db(auth.username, auth.password)
if user is not None:
login_user(user, remember=False)
return user


def requires_authentication(function: T):
"""Decorate functions that require authentication."""
return fab_basic_auth.auth_current_user()

@wraps(function)
def decorated(*args, **kwargs):
if auth_current_user() is not None:
return function(*args, **kwargs)
else:
return Response("Unauthorized", 401, {"WWW-Authenticate": "Basic"})

return cast(T, decorated)
def requires_authentication(function: Callable):
return fab_basic_auth.requires_authentication(function)
34 changes: 19 additions & 15 deletions airflow/api/auth/backend/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,31 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Default authentication backend - everything is allowed."""
from __future__ import annotations
"""
This module is deprecated.

from functools import wraps
from typing import Any, Callable, TypeVar, cast
Please use :mod:`airflow.auth.managers.fab.api.auth.backend.default` instead.
"""
from __future__ import annotations

CLIENT_AUTH: tuple[str, str] | Any | None = None
import warnings
from typing import Any, Callable

import airflow.auth.managers.fab.api.auth.backend.default as fab_default_auth
from airflow.exceptions import RemovedInAirflow3Warning

def init_app(_):
"""Initialize authentication backend."""

CLIENT_AUTH: tuple[str, str] | Any | None = None

T = TypeVar("T", bound=Callable)
warnings.warn(
"This module is deprecated. Please use `airflow.auth.managers.fab.api.auth.backend.default` instead.",
RemovedInAirflow3Warning,
stacklevel=2,
)


def requires_authentication(function: T):
"""Decorate functions that require authentication."""
def init_app(_):
fab_default_auth.init_app(_)

@wraps(function)
def decorated(*args, **kwargs):
return function(*args, **kwargs)

return cast(T, decorated)
def requires_authentication(function: Callable):
return fab_default_auth.requires_authentication(function)
34 changes: 18 additions & 16 deletions airflow/api/auth/backend/deny_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,31 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Authentication backend that denies all requests."""
"""
This module is deprecated.

Please use :mod:`airflow.auth.managers.fab.api.auth.backend.deny_all` instead.
"""
from __future__ import annotations

from functools import wraps
from typing import Any, Callable, TypeVar, cast
import warnings
from typing import Any, Callable

from flask import Response
import airflow.auth.managers.fab.api.auth.backend.deny_all as fab_deny_all_auth
from airflow.exceptions import RemovedInAirflow3Warning

CLIENT_AUTH: tuple[str, str] | Any | None = None


def init_app(_):
"""Initialize authentication."""


T = TypeVar("T", bound=Callable)
warnings.warn(
"This module is deprecated. Please use `airflow.auth.managers.fab.api.auth.backend.deny_all` instead.",
RemovedInAirflow3Warning,
stacklevel=2,
)


def requires_authentication(function: T):
"""Decorate functions that require authentication."""
def init_app(_):
fab_deny_all_auth.init_app(_)

@wraps(function)
def decorated(*args, **kwargs):
return Response("Forbidden", 403)

return cast(T, decorated)
def requires_authentication(function: Callable):
return fab_deny_all_auth.requires_authentication(function)
119 changes: 19 additions & 100 deletions airflow/api/auth/backend/kerberos_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
# under the License.
from __future__ import annotations

from airflow.utils.airflow_flask_app import get_airflow_app
import warnings

from airflow.exceptions import RemovedInAirflow3Warning

#
# Copyright (c) 2013, Michael Komitee
Expand All @@ -42,113 +44,30 @@
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Kerberos authentication module"""
import logging
import os
from functools import wraps
from typing import Any, Callable, TypeVar, cast

import kerberos
from flask import Response, _request_ctx_stack as stack, g, make_response, request # type: ignore
from requests_kerberos import HTTPKerberosAuth
"""
This module is deprecated.

from airflow.configuration import conf
from airflow.utils.net import getfqdn
Please use :mod:`airflow.auth.managers.fab.api.auth.backend.kerberos_auth` instead.
"""
from typing import Any, Callable

log = logging.getLogger(__name__)
from requests_kerberos import HTTPKerberosAuth

import airflow.auth.managers.fab.api.auth.backend.kerberos_auth as fab_kerberos_auth

CLIENT_AUTH: tuple[str, str] | Any | None = HTTPKerberosAuth(service="airflow")


class KerberosService:
"""Class to keep information about the Kerberos Service initialized."""

def __init__(self):
self.service_name = None


# Stores currently initialized Kerberos Service
_KERBEROS_SERVICE = KerberosService()
warnings.warn(
"This module is deprecated. Please use `airflow.auth.managers.fab.api.auth.backend.kerberos_auth` "
"instead.",
RemovedInAirflow3Warning,
stacklevel=2,
)


def init_app(app):
"""Initialize application with kerberos."""
hostname = app.config.get("SERVER_NAME")
if not hostname:
hostname = getfqdn()
log.info("Kerberos: hostname %s", hostname)

service = "airflow"

_KERBEROS_SERVICE.service_name = f"{service}@{hostname}"

if "KRB5_KTNAME" not in os.environ:
os.environ["KRB5_KTNAME"] = conf.get("kerberos", "keytab")

try:
log.info("Kerberos init: %s %s", service, hostname)
principal = kerberos.getServerPrincipalDetails(service, hostname)
except kerberos.KrbError as err:
log.warning("Kerberos: %s", err)
else:
log.info("Kerberos API: server is %s", principal)


def _unauthorized():
"""Indicate that authorization is required."""
return Response("Unauthorized", 401, {"WWW-Authenticate": "Negotiate"})


def _forbidden():
return Response("Forbidden", 403)


def _gssapi_authenticate(token):
state = None
ctx = stack.top
try:
return_code, state = kerberos.authGSSServerInit(_KERBEROS_SERVICE.service_name)
if return_code != kerberos.AUTH_GSS_COMPLETE:
return None
return_code = kerberos.authGSSServerStep(state, token)
if return_code == kerberos.AUTH_GSS_COMPLETE:
ctx.kerberos_token = kerberos.authGSSServerResponse(state)
ctx.kerberos_user = kerberos.authGSSServerUserName(state)
return return_code
if return_code == kerberos.AUTH_GSS_CONTINUE:
return kerberos.AUTH_GSS_CONTINUE
return None
except kerberos.GSSError:
return None
finally:
if state:
kerberos.authGSSServerClean(state)


T = TypeVar("T", bound=Callable)


def requires_authentication(function: T):
"""Decorate functions that require authentication with Kerberos."""

@wraps(function)
def decorated(*args, **kwargs):
header = request.headers.get("Authorization")
if header:
ctx = stack.top
token = "".join(header.split()[1:])
return_code = _gssapi_authenticate(token)
if return_code == kerberos.AUTH_GSS_COMPLETE:
g.user = get_airflow_app().appbuilder.sm.find_user(username=ctx.kerberos_user)
response = function(*args, **kwargs)
response = make_response(response)
if ctx.kerberos_token is not None:
response.headers["WWW-Authenticate"] = f"negotiate {ctx.kerberos_token}"
fab_kerberos_auth.init_app(app)

return response
if return_code != kerberos.AUTH_GSS_CONTINUE:
return _forbidden()
return _unauthorized()

return cast(T, decorated)
def requires_authentication(function: Callable):
return fab_kerberos_auth.requires_authentication(function)
17 changes: 17 additions & 0 deletions airflow/auth/managers/fab/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
17 changes: 17 additions & 0 deletions airflow/auth/managers/fab/api/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
17 changes: 17 additions & 0 deletions airflow/auth/managers/fab/api/auth/backend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
Loading
Loading