From 8741e9c1761931c7cff135d53b589053a04f58c1 Mon Sep 17 00:00:00 2001 From: Vincent <97131062+vincbeck@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:52:43 -0700 Subject: [PATCH] Handle `AUTH_ROLE_PUBLIC` in FAB auth manager (#42280) --- .../endpoints/forward_to_fab_endpoint.py | 131 ------ airflow/api_connexion/openapi/v1.yaml | 310 ------------ airflow/api_connexion/security.py | 5 - .../default_webserver_config.py | 2 +- .../api/auth/backend/basic_auth.py | 6 +- .../api/auth/backend/kerberos_auth.py | 6 +- .../fab/auth_manager/fab_auth_manager.py | 17 +- .../fab/auth_manager/models/anonymous_user.py | 8 +- .../auth_manager/security_manager/override.py | 6 +- airflow/www/static/js/types/api-generated.ts | 441 ------------------ airflow/www/utils.py | 11 +- airflow/www/views.py | 4 +- newsfragments/42280.significant.rst | 5 + tests/api_connexion/conftest.py | 11 - .../endpoints/test_config_endpoint.py | 22 - .../endpoints/test_connection_endpoint.py | 89 ---- .../endpoints/test_dag_endpoint.py | 99 ---- .../endpoints/test_dag_run_endpoint.py | 185 -------- .../endpoints/test_dag_source_endpoint.py | 16 - .../endpoints/test_dag_warning_endpoint.py | 12 - .../endpoints/test_dataset_endpoint.py | 184 -------- .../endpoints/test_event_log_endpoint.py | 44 -- .../endpoints/test_forward_to_fab_endpoint.py | 238 ---------- .../test_role_and_permission_endpoint.py | 12 +- .../api_endpoints/test_user_endpoint.py | 1 + tests/providers/fab/auth_manager/conftest.py | 9 +- .../fab/auth_manager/test_fab_auth_manager.py | 10 +- 27 files changed, 60 insertions(+), 1824 deletions(-) delete mode 100644 airflow/api_connexion/endpoints/forward_to_fab_endpoint.py create mode 100644 newsfragments/42280.significant.rst delete mode 100644 tests/api_connexion/endpoints/test_forward_to_fab_endpoint.py diff --git a/airflow/api_connexion/endpoints/forward_to_fab_endpoint.py b/airflow/api_connexion/endpoints/forward_to_fab_endpoint.py deleted file mode 100644 index 9785a5b05312..000000000000 --- a/airflow/api_connexion/endpoints/forward_to_fab_endpoint.py +++ /dev/null @@ -1,131 +0,0 @@ -# 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. -from __future__ import annotations - -import warnings -from typing import TYPE_CHECKING - -from airflow.api_connexion.exceptions import BadRequest -from airflow.providers.fab.auth_manager.api_endpoints import role_and_permission_endpoint, user_endpoint -from airflow.www.extensions.init_auth_manager import get_auth_manager - -if TYPE_CHECKING: - from typing import Callable - - from airflow.api_connexion.types import APIResponse - - -def _require_fab(func: Callable) -> Callable: - """ - Raise an HTTP error 400 if the auth manager is not FAB. - - Intended to decorate endpoints that have been migrated from Airflow API to FAB API. - """ - - def inner(*args, **kwargs): - from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager - - auth_mgr = get_auth_manager() - if not isinstance(auth_mgr, FabAuthManager): - raise BadRequest( - detail="This endpoint is only available when using the default auth manager FabAuthManager." - ) - else: - warnings.warn( - "This API endpoint is deprecated. " - "Please use the API under /auth/fab/v1 instead for this operation.", - DeprecationWarning, - stacklevel=1, # This decorator wrapped multiple times, better point to this file - ) - return func(*args, **kwargs) - - return inner - - -### role - - -@_require_fab -def get_role(**kwargs) -> APIResponse: - """Get role.""" - return role_and_permission_endpoint.get_role(**kwargs) - - -@_require_fab -def get_roles(**kwargs) -> APIResponse: - """Get roles.""" - return role_and_permission_endpoint.get_roles(**kwargs) - - -@_require_fab -def delete_role(**kwargs) -> APIResponse: - """Delete a role.""" - return role_and_permission_endpoint.delete_role(**kwargs) - - -@_require_fab -def patch_role(**kwargs) -> APIResponse: - """Update a role.""" - kwargs.pop("body", None) - return role_and_permission_endpoint.patch_role(**kwargs) - - -@_require_fab -def post_role(**kwargs) -> APIResponse: - """Create a new role.""" - kwargs.pop("body", None) - return role_and_permission_endpoint.post_role(**kwargs) - - -### permissions -@_require_fab -def get_permissions(**kwargs) -> APIResponse: - """Get permissions.""" - return role_and_permission_endpoint.get_permissions(**kwargs) - - -### user -@_require_fab -def get_user(**kwargs) -> APIResponse: - """Get a user.""" - return user_endpoint.get_user(**kwargs) - - -@_require_fab -def get_users(**kwargs) -> APIResponse: - """Get users.""" - return user_endpoint.get_users(**kwargs) - - -@_require_fab -def post_user(**kwargs) -> APIResponse: - """Create a new user.""" - kwargs.pop("body", None) - return user_endpoint.post_user(**kwargs) - - -@_require_fab -def patch_user(**kwargs) -> APIResponse: - """Update a user.""" - kwargs.pop("body", None) - return user_endpoint.patch_user(**kwargs) - - -@_require_fab -def delete_user(**kwargs) -> APIResponse: - """Delete a user.""" - return user_endpoint.delete_user(**kwargs) diff --git a/airflow/api_connexion/openapi/v1.yaml b/airflow/api_connexion/openapi/v1.yaml index 24b9c1be0d33..07cb7fcb747a 100644 --- a/airflow/api_connexion/openapi/v1.yaml +++ b/airflow/api_connexion/openapi/v1.yaml @@ -2591,316 +2591,6 @@ paths: "404": $ref: "#/components/responses/NotFound" - /roles: - get: - deprecated: true - summary: List roles - description: | - Get a list of roles. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: get_roles - tags: [Role] - parameters: - - $ref: "#/components/parameters/PageLimit" - - $ref: "#/components/parameters/PageOffset" - - $ref: "#/components/parameters/OrderBy" - responses: - "200": - description: Success. - content: - application/json: - schema: - $ref: "#/components/schemas/RoleCollection" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - - post: - deprecated: true - summary: Create a role - description: | - Create a new role. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: post_role - tags: [Role] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Role" - responses: - "200": - description: Success. - content: - application/json: - schema: - $ref: "#/components/schemas/Role" - "400": - $ref: "#/components/responses/BadRequest" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - - /roles/{role_name}: - parameters: - - $ref: "#/components/parameters/RoleName" - - get: - deprecated: true - summary: Get a role - description: | - Get a role. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: get_role - tags: [Role] - responses: - "200": - description: Success. - content: - application/json: - schema: - $ref: "#/components/schemas/Role" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - "404": - $ref: "#/components/responses/NotFound" - - patch: - deprecated: true - summary: Update a role - description: | - Update a role. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: patch_role - tags: [Role] - parameters: - - $ref: "#/components/parameters/UpdateMask" - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Role" - - responses: - "200": - description: Success. - content: - application/json: - schema: - $ref: "#/components/schemas/Role" - "400": - $ref: "#/components/responses/BadRequest" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - "404": - $ref: "#/components/responses/NotFound" - - delete: - deprecated: true - summary: Delete a role - description: | - Delete a role. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: delete_role - tags: [Role] - responses: - "204": - description: Success. - "400": - $ref: "#/components/responses/BadRequest" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - "404": - $ref: "#/components/responses/NotFound" - - /permissions: - get: - deprecated: true - summary: List permissions - description: | - Get a list of permissions. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: get_permissions - tags: [Permission] - parameters: - - $ref: "#/components/parameters/PageLimit" - - $ref: "#/components/parameters/PageOffset" - responses: - "200": - description: Success. - content: - application/json: - schema: - $ref: "#/components/schemas/ActionCollection" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - - /users: - get: - deprecated: true - summary: List users - description: | - Get a list of users. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: get_users - tags: [User] - parameters: - - $ref: "#/components/parameters/PageLimit" - - $ref: "#/components/parameters/PageOffset" - - $ref: "#/components/parameters/OrderBy" - responses: - "200": - description: Success. - content: - application/json: - schema: - $ref: "#/components/schemas/UserCollection" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - - post: - deprecated: true - summary: Create a user - description: | - Create a new user with unique username and email. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: post_user - tags: [User] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/User" - responses: - "200": - description: Success. - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - $ref: "#/components/responses/BadRequest" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - "409": - $ref: "#/components/responses/AlreadyExists" - - /users/{username}: - parameters: - - $ref: "#/components/parameters/Username" - get: - deprecated: true - summary: Get a user - description: | - Get a user with a specific username. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: get_user - tags: [User] - responses: - "200": - description: Success. - content: - application/json: - schema: - $ref: "#/components/schemas/UserCollectionItem" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - "404": - $ref: "#/components/responses/NotFound" - - patch: - deprecated: true - summary: Update a user - description: | - Update fields for a user. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: patch_user - tags: [User] - parameters: - - $ref: "#/components/parameters/UpdateMask" - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/User" - responses: - "200": - description: Success. - content: - application/json: - schema: - $ref: "#/components/schemas/UserCollectionItem" - "400": - $ref: "#/components/responses/BadRequest" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - "404": - $ref: "#/components/responses/NotFound" - - delete: - deprecated: true - summary: Delete a user - description: | - Delete a user with a specific username. - - *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - x-openapi-router-controller: airflow.api_connexion.endpoints.forward_to_fab_endpoint - operationId: delete_user - tags: [User] - responses: - "204": - description: Success. - "400": - $ref: "#/components/responses/BadRequest" - "401": - $ref: "#/components/responses/Unauthenticated" - "403": - $ref: "#/components/responses/PermissionDenied" - "404": - $ref: "#/components/responses/NotFound" - components: # Reusable schemas (data models) schemas: diff --git a/airflow/api_connexion/security.py b/airflow/api_connexion/security.py index c6474fd60074..7b0a026e095d 100644 --- a/airflow/api_connexion/security.py +++ b/airflow/api_connexion/security.py @@ -48,11 +48,6 @@ def check_authentication() -> None: if response.status_code == 200: return - # Even if the current_user is anonymous, the AUTH_ROLE_PUBLIC might still have permission. - appbuilder = get_airflow_app().appbuilder - if appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None): - return - # since this handler only checks authentication, not authorization, # we should always return 401 raise Unauthenticated(headers=response.headers) diff --git a/airflow/config_templates/default_webserver_config.py b/airflow/config_templates/default_webserver_config.py index 71bdf9e99d08..5ef855ae4ab7 100644 --- a/airflow/config_templates/default_webserver_config.py +++ b/airflow/config_templates/default_webserver_config.py @@ -36,7 +36,7 @@ WTF_CSRF_TIME_LIMIT = None # ---------------------------------------------------- -# AUTHENTICATION CONFIG +# AUTHENTICATION CONFIG (specific to FAB auth manager) # ---------------------------------------------------- # For details on how to set up each of the following authentication, see # http://flask-appbuilder.readthedocs.io/en/latest/security.html# authentication-methods diff --git a/airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py b/airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py index ff7c2cc3b374..3a0328fe9962 100644 --- a/airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py +++ b/airflow/providers/fab/auth_manager/api/auth/backend/basic_auth.py @@ -21,7 +21,7 @@ from functools import wraps from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast -from flask import Response, request +from flask import Response, current_app, request from flask_appbuilder.const import AUTH_LDAP from flask_login import login_user @@ -62,7 +62,9 @@ def requires_authentication(function: T): @wraps(function) def decorated(*args, **kwargs): - if auth_current_user() is not None: + if auth_current_user() is not None or current_app.appbuilder.get_app.config.get( + "AUTH_ROLE_PUBLIC", None + ): return function(*args, **kwargs) else: return Response("Unauthorized", 401, {"WWW-Authenticate": "Basic"}) diff --git a/airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py b/airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py index b2c45853015f..d8d5a95ee676 100644 --- a/airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py +++ b/airflow/providers/fab/auth_manager/api/auth/backend/kerberos_auth.py @@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, Any, Callable, NamedTuple, TypeVar, cast import kerberos -from flask import Response, g, make_response, request +from flask import Response, current_app, g, make_response, request from requests_kerberos import HTTPKerberosAuth from airflow.configuration import conf @@ -124,6 +124,10 @@ def requires_authentication(function: T, find_user: Callable[[str], BaseUser] | @wraps(function) def decorated(*args, **kwargs): + if current_app.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None): + response = function(*args, **kwargs) + return make_response(response) + header = request.headers.get("Authorization") if header: token = "".join(header.split()[1:]) diff --git a/airflow/providers/fab/auth_manager/fab_auth_manager.py b/airflow/providers/fab/auth_manager/fab_auth_manager.py index 336437061c2f..2de8db2f5641 100644 --- a/airflow/providers/fab/auth_manager/fab_auth_manager.py +++ b/airflow/providers/fab/auth_manager/fab_auth_manager.py @@ -25,6 +25,7 @@ import packaging.version from connexion import FlaskApi from flask import Blueprint, url_for +from packaging.version import Version from sqlalchemy import select from sqlalchemy.orm import Session, joinedload @@ -84,6 +85,7 @@ ) from airflow.utils.session import NEW_SESSION, provide_session from airflow.utils.yaml import safe_load +from airflow.version import version from airflow.www.constants import SWAGGER_BUNDLE, SWAGGER_ENABLED from airflow.www.extensions.init_views import _CustomErrorRequestBodyValidator, _LazyResolver @@ -189,7 +191,12 @@ def init(self) -> None: def is_logged_in(self) -> bool: """Return whether the user is logged in.""" user = self.get_user() - return not user.is_anonymous and user.is_active + if Version(Version(version).base_version) < Version("3.0.0"): + return not user.is_anonymous and user.is_active + else: + return self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None) or ( + not user.is_anonymous and user.is_active + ) def is_authorized_configuration( self, @@ -374,7 +381,9 @@ def get_url_logout(self): def get_url_user_profile(self) -> str | None: """Return the url to a page displaying info about the current user.""" - if not self.security_manager.user_view: + if not self.security_manager.user_view or self.appbuilder.get_app.config.get( + "AUTH_ROLE_PUBLIC", None + ): return None return url_for(f"{self.security_manager.user_view.endpoint}.userinfo") @@ -532,10 +541,6 @@ def _sync_appbuilder_roles(self): # Otherwise, when the name of a view or menu is changed, the framework # will add the new Views and Menus names to the backend, but will not # delete the old ones. - from packaging.version import Version - - from airflow.version import version - if Version(Version(version).base_version) >= Version("3.0.0"): fallback = None else: diff --git a/airflow/providers/fab/auth_manager/models/anonymous_user.py b/airflow/providers/fab/auth_manager/models/anonymous_user.py index ba75de0d3c6e..2f294fd9e5d0 100644 --- a/airflow/providers/fab/auth_manager/models/anonymous_user.py +++ b/airflow/providers/fab/auth_manager/models/anonymous_user.py @@ -29,10 +29,13 @@ class AnonymousUser(AnonymousUserMixin, BaseUser): _roles: set[tuple[str, str]] = set() _perms: set[tuple[str, str]] = set() + first_name = "Anonymous" + last_name = "" + @property def roles(self): if not self._roles: - public_role = current_app.appbuilder.get_app.config["AUTH_ROLE_PUBLIC"] + public_role = current_app.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None) self._roles = {current_app.appbuilder.sm.find_role(public_role)} if public_role else set() return self._roles @@ -48,3 +51,6 @@ def perms(self): (perm.action.name, perm.resource.name) for role in self.roles for perm in role.permissions } return self._perms + + def get_name(self) -> str: + return "Anonymous" diff --git a/airflow/providers/fab/auth_manager/security_manager/override.py b/airflow/providers/fab/auth_manager/security_manager/override.py index fad32c9f55ba..0f4b79b4f1f6 100644 --- a/airflow/providers/fab/auth_manager/security_manager/override.py +++ b/airflow/providers/fab/auth_manager/security_manager/override.py @@ -609,7 +609,7 @@ def auth_rate_limit(self) -> str: @property def auth_role_public(self): """Get the public role.""" - return self.appbuilder.get_app.config["AUTH_ROLE_PUBLIC"] + return self.appbuilder.get_app.config.get("AUTH_ROLE_PUBLIC", None) @property def oauth_providers(self): @@ -832,7 +832,6 @@ def _init_config(self): app = self.appbuilder.get_app # Base Security Config app.config.setdefault("AUTH_ROLE_ADMIN", "Admin") - app.config.setdefault("AUTH_ROLE_PUBLIC", "Public") app.config.setdefault("AUTH_TYPE", AUTH_DB) # Self Registration app.config.setdefault("AUTH_USER_REGISTRATION", False) @@ -955,7 +954,8 @@ def create_db(self): self.add_role(role_name) if self.auth_role_admin not in self._builtin_roles: self.add_role(self.auth_role_admin) - self.add_role(self.auth_role_public) + if self.auth_role_public: + self.add_role(self.auth_role_public) if self.count_users() == 0 and self.auth_role_public != self.auth_role_admin: log.warning(const.LOGMSG_WAR_SEC_NO_USER) except Exception: diff --git a/airflow/www/static/js/types/api-generated.ts b/airflow/www/static/js/types/api-generated.ts index 8d912574d20f..09def0ac66b6 100644 --- a/airflow/www/static/js/types/api-generated.ts +++ b/airflow/www/static/js/types/api-generated.ts @@ -781,98 +781,6 @@ export interface paths { */ get: operations["get_plugins"]; }; - "/roles": { - /** - * Get a list of roles. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get: operations["get_roles"]; - /** - * Create a new role. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - post: operations["post_role"]; - }; - "/roles/{role_name}": { - /** - * Get a role. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get: operations["get_role"]; - /** - * Delete a role. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - delete: operations["delete_role"]; - /** - * Update a role. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - patch: operations["patch_role"]; - parameters: { - path: { - /** The role name */ - role_name: components["parameters"]["RoleName"]; - }; - }; - }; - "/permissions": { - /** - * Get a list of permissions. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get: operations["get_permissions"]; - }; - "/users": { - /** - * Get a list of users. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get: operations["get_users"]; - /** - * Create a new user with unique username and email. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - post: operations["post_user"]; - }; - "/users/{username}": { - /** - * Get a user with a specific username. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get: operations["get_user"]; - /** - * Delete a user with a specific username. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - delete: operations["delete_user"]; - /** - * Update fields for a user. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - patch: operations["patch_user"]; - parameters: { - path: { - /** - * The username of the user. - * - * *New in version 2.1.0* - */ - username: components["parameters"]["Username"]; - }; - }; - }; } export interface components { @@ -5123,318 +5031,6 @@ export interface operations { 404: components["responses"]["NotFound"]; }; }; - /** - * Get a list of roles. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get_roles: { - parameters: { - query: { - /** The numbers of items to return. */ - limit?: components["parameters"]["PageLimit"]; - /** The number of items to skip before starting to collect the result set. */ - offset?: components["parameters"]["PageOffset"]; - /** - * The name of the field to order the results by. - * Prefix a field name with `-` to reverse the sort order. - * - * *New in version 2.1.0* - */ - order_by?: components["parameters"]["OrderBy"]; - }; - }; - responses: { - /** Success. */ - 200: { - content: { - "application/json": components["schemas"]["RoleCollection"]; - }; - }; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - }; - }; - /** - * Create a new role. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - post_role: { - responses: { - /** Success. */ - 200: { - content: { - "application/json": components["schemas"]["Role"]; - }; - }; - 400: components["responses"]["BadRequest"]; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Role"]; - }; - }; - }; - /** - * Get a role. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get_role: { - parameters: { - path: { - /** The role name */ - role_name: components["parameters"]["RoleName"]; - }; - }; - responses: { - /** Success. */ - 200: { - content: { - "application/json": components["schemas"]["Role"]; - }; - }; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - 404: components["responses"]["NotFound"]; - }; - }; - /** - * Delete a role. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - delete_role: { - parameters: { - path: { - /** The role name */ - role_name: components["parameters"]["RoleName"]; - }; - }; - responses: { - /** Success. */ - 204: never; - 400: components["responses"]["BadRequest"]; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - 404: components["responses"]["NotFound"]; - }; - }; - /** - * Update a role. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - patch_role: { - parameters: { - path: { - /** The role name */ - role_name: components["parameters"]["RoleName"]; - }; - query: { - /** - * The fields to update on the resource. If absent or empty, all modifiable fields are updated. - * A comma-separated list of fully qualified names of fields. - */ - update_mask?: components["parameters"]["UpdateMask"]; - }; - }; - responses: { - /** Success. */ - 200: { - content: { - "application/json": components["schemas"]["Role"]; - }; - }; - 400: components["responses"]["BadRequest"]; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - 404: components["responses"]["NotFound"]; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Role"]; - }; - }; - }; - /** - * Get a list of permissions. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get_permissions: { - parameters: { - query: { - /** The numbers of items to return. */ - limit?: components["parameters"]["PageLimit"]; - /** The number of items to skip before starting to collect the result set. */ - offset?: components["parameters"]["PageOffset"]; - }; - }; - responses: { - /** Success. */ - 200: { - content: { - "application/json": components["schemas"]["ActionCollection"]; - }; - }; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - }; - }; - /** - * Get a list of users. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get_users: { - parameters: { - query: { - /** The numbers of items to return. */ - limit?: components["parameters"]["PageLimit"]; - /** The number of items to skip before starting to collect the result set. */ - offset?: components["parameters"]["PageOffset"]; - /** - * The name of the field to order the results by. - * Prefix a field name with `-` to reverse the sort order. - * - * *New in version 2.1.0* - */ - order_by?: components["parameters"]["OrderBy"]; - }; - }; - responses: { - /** Success. */ - 200: { - content: { - "application/json": components["schemas"]["UserCollection"]; - }; - }; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - }; - }; - /** - * Create a new user with unique username and email. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - post_user: { - responses: { - /** Success. */ - 200: { - content: { - "application/json": components["schemas"]["User"]; - }; - }; - 400: components["responses"]["BadRequest"]; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - 409: components["responses"]["AlreadyExists"]; - }; - requestBody: { - content: { - "application/json": components["schemas"]["User"]; - }; - }; - }; - /** - * Get a user with a specific username. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - get_user: { - parameters: { - path: { - /** - * The username of the user. - * - * *New in version 2.1.0* - */ - username: components["parameters"]["Username"]; - }; - }; - responses: { - /** Success. */ - 200: { - content: { - "application/json": components["schemas"]["UserCollectionItem"]; - }; - }; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - 404: components["responses"]["NotFound"]; - }; - }; - /** - * Delete a user with a specific username. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - delete_user: { - parameters: { - path: { - /** - * The username of the user. - * - * *New in version 2.1.0* - */ - username: components["parameters"]["Username"]; - }; - }; - responses: { - /** Success. */ - 204: never; - 400: components["responses"]["BadRequest"]; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - 404: components["responses"]["NotFound"]; - }; - }; - /** - * Update fields for a user. - * - * *This API endpoint is deprecated, please use the endpoint `/auth/fab/v1` for this operation instead.* - */ - patch_user: { - parameters: { - path: { - /** - * The username of the user. - * - * *New in version 2.1.0* - */ - username: components["parameters"]["Username"]; - }; - query: { - /** - * The fields to update on the resource. If absent or empty, all modifiable fields are updated. - * A comma-separated list of fully qualified names of fields. - */ - update_mask?: components["parameters"]["UpdateMask"]; - }; - }; - responses: { - /** Success. */ - 200: { - content: { - "application/json": components["schemas"]["UserCollectionItem"]; - }; - }; - 400: components["responses"]["BadRequest"]; - 401: components["responses"]["Unauthenticated"]; - 403: components["responses"]["PermissionDenied"]; - 404: components["responses"]["NotFound"]; - }; - requestBody: { - content: { - "application/json": components["schemas"]["User"]; - }; - }; - }; } export interface external {} @@ -5970,40 +5566,3 @@ export type GetValueVariables = CamelCasedPropertiesDeep< export type GetPluginsVariables = CamelCasedPropertiesDeep< operations["get_plugins"]["parameters"]["query"] >; -export type GetRolesVariables = CamelCasedPropertiesDeep< - operations["get_roles"]["parameters"]["query"] ->; -export type PostRoleVariables = CamelCasedPropertiesDeep< - operations["post_role"]["requestBody"]["content"]["application/json"] ->; -export type GetRoleVariables = CamelCasedPropertiesDeep< - operations["get_role"]["parameters"]["path"] ->; -export type DeleteRoleVariables = CamelCasedPropertiesDeep< - operations["delete_role"]["parameters"]["path"] ->; -export type PatchRoleVariables = CamelCasedPropertiesDeep< - operations["patch_role"]["parameters"]["path"] & - operations["patch_role"]["parameters"]["query"] & - operations["patch_role"]["requestBody"]["content"]["application/json"] ->; -export type GetPermissionsVariables = CamelCasedPropertiesDeep< - operations["get_permissions"]["parameters"]["query"] ->; -export type GetUsersVariables = CamelCasedPropertiesDeep< - operations["get_users"]["parameters"]["query"] ->; -export type PostUserVariables = CamelCasedPropertiesDeep< - operations["post_user"]["requestBody"]["content"]["application/json"] ->; -export type GetUserVariables = CamelCasedPropertiesDeep< - operations["get_user"]["parameters"]["path"] ->; -export type DeleteUserVariables = CamelCasedPropertiesDeep< - operations["delete_user"]["parameters"]["path"] ->; -export type PatchUserVariables = CamelCasedPropertiesDeep< - operations["patch_user"]["parameters"]["path"] & - operations["patch_user"]["parameters"]["query"] & - operations["patch_user"]["requestBody"]["content"]["application/json"] ->; diff --git a/airflow/www/utils.py b/airflow/www/utils.py index ef057adbf36f..981dc030a210 100644 --- a/airflow/www/utils.py +++ b/airflow/www/utils.py @@ -61,8 +61,6 @@ from sqlalchemy.sql import Select from sqlalchemy.sql.operators import ColumnOperators - from airflow.www.extensions.init_appbuilder import AirflowAppBuilder - TI = TaskInstance @@ -924,21 +922,16 @@ def __init__( self.html = html self.message = Markup(message) if html else message - def should_show(self, appbuilder: AirflowAppBuilder) -> bool: + def should_show(self) -> bool: """ Determine if the user should see the message. - The decision is based on the user's role. If ``AUTH_ROLE_PUBLIC`` is - set in ``webserver_config.py``, An anonymous user would have the - ``AUTH_ROLE_PUBLIC`` role. + The decision is based on the user's role. """ if self.roles: current_user = get_auth_manager().get_user() if current_user is not None: user_roles = {r.name for r in getattr(current_user, "roles", [])} - elif "AUTH_ROLE_PUBLIC" in appbuilder.get_app.config: - # If the current_user is anonymous, assign AUTH_ROLE_PUBLIC role (if it exists) to them - user_roles = {appbuilder.get_app.config["AUTH_ROLE_PUBLIC"]} else: # Unable to obtain user role - default to not showing return False diff --git a/airflow/www/views.py b/airflow/www/views.py index 65c677c9e334..0ef37f71d336 100644 --- a/airflow/www/views.py +++ b/airflow/www/views.py @@ -1093,9 +1093,7 @@ def index(self): section="webserver", key="instance_name_has_markup", fallback=False ) - dashboard_alerts = [ - fm for fm in settings.DASHBOARD_UIALERTS if fm.should_show(get_airflow_app().appbuilder) - ] + dashboard_alerts = [fm for fm in settings.DASHBOARD_UIALERTS if fm.should_show()] def _iter_parsed_moved_data_table_names(): for table_name in inspect(session.get_bind()).get_table_names(): diff --git a/newsfragments/42280.significant.rst b/newsfragments/42280.significant.rst new file mode 100644 index 000000000000..00d80d259b12 --- /dev/null +++ b/newsfragments/42280.significant.rst @@ -0,0 +1,5 @@ +Removed deprecated Rest API endpoints: + +* /api/v1/roles. Use /auth/fab/v1/roles instead +* /api/v1/permissions. Use /auth/fab/v1/permissions instead +* /api/v1/users. Use /auth/fab/v1/users instead diff --git a/tests/api_connexion/conftest.py b/tests/api_connexion/conftest.py index df0c3462f5eb..38e7b58cb598 100644 --- a/tests/api_connexion/conftest.py +++ b/tests/api_connexion/conftest.py @@ -58,14 +58,3 @@ def dagbag(): DagBag(include_examples=True, read_dags_from_db=False).sync_to_db() return DagBag(include_examples=True, read_dags_from_db=True) - - -@pytest.fixture -def set_auto_role_public(request): - app = request.getfixturevalue("minimal_app_for_api") - auto_role_public = app.config["AUTH_ROLE_PUBLIC"] - app.config["AUTH_ROLE_PUBLIC"] = request.param - - yield - - app.config["AUTH_ROLE_PUBLIC"] = auto_role_public diff --git a/tests/api_connexion/endpoints/test_config_endpoint.py b/tests/api_connexion/endpoints/test_config_endpoint.py index 42d0922de8ce..475753a4a902 100644 --- a/tests/api_connexion/endpoints/test_config_endpoint.py +++ b/tests/api_connexion/endpoints/test_config_endpoint.py @@ -225,16 +225,6 @@ def test_should_respond_403_when_expose_config_off(self): assert response.status_code == 403 assert "chose not to expose" in response.json["detail"] - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): - response = self.client.get("/api/v1/config", headers={"Accept": "application/json"}) - - assert response.status_code == expected_status_code - class TestGetValue: @pytest.fixture(autouse=True) @@ -352,15 +342,3 @@ def test_should_respond_403_when_expose_config_off(self): ) assert response.status_code == 403 assert "chose not to expose" in response.json["detail"] - - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): - response = self.client.get( - "/api/v1/config/section/smtp/option/smtp_mail_from", headers={"Accept": "application/json"} - ) - - assert response.status_code == expected_status_code diff --git a/tests/api_connexion/endpoints/test_connection_endpoint.py b/tests/api_connexion/endpoints/test_connection_endpoint.py index 75a745c76dc6..a19b046aa274 100644 --- a/tests/api_connexion/endpoints/test_connection_endpoint.py +++ b/tests/api_connexion/endpoints/test_connection_endpoint.py @@ -112,22 +112,6 @@ def test_should_raise_403_forbidden(self): ) assert response.status_code == 403 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 204)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - connection_model = Connection(conn_id="test-connection", conn_type="test_type") - session.add(connection_model) - session.commit() - conn = session.query(Connection).all() - assert len(conn) == 1 - - response = self.client.delete("/api/v1/connections/test-connection") - - assert response.status_code == expected_status_code - class TestGetConnection(TestConnectionEndpoint): def test_should_respond_200(self, session): @@ -194,31 +178,6 @@ def test_should_raises_401_unauthenticated(self): assert_401(response) - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - connection_model = Connection( - conn_id="test-connection-id", - conn_type="mysql", - description="test description", - host="mysql", - login="login", - schema="testschema", - port=80, - extra='{"param": "value"}', - ) - session.add(connection_model) - session.commit() - result = session.query(Connection).all() - assert len(result) == 1 - - response = self.client.get("/api/v1/connections/test-connection-id") - - assert response.status_code == expected_status_code - class TestGetConnections(TestConnectionEndpoint): def test_should_respond_200(self, session): @@ -297,16 +256,6 @@ def test_should_raises_401_unauthenticated(self): assert_401(response) - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): - response = self.client.get("/api/v1/connections") - - assert response.status_code == expected_status_code - class TestGetConnectionsPagination(TestConnectionEndpoint): @pytest.mark.parametrize( @@ -580,21 +529,6 @@ def test_should_raises_401_unauthenticated(self, session): assert_401(response) - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - self._create_connection(session) - - response = self.client.patch( - "/api/v1/connections/test-connection-id", - json={"connection_id": "test-connection-id", "conn_type": "test_type", "extra": '{"key": "var"}'}, - ) - - assert response.status_code == expected_status_code - class TestPostConnection(TestConnectionEndpoint): def test_post_should_respond_200(self, session): @@ -676,18 +610,6 @@ def test_should_raises_401_unauthenticated(self): assert_401(response) - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): - response = self.client.post( - "/api/v1/connections", json={"connection_id": "test-connection-id", "conn_type": "test_type"} - ) - - assert response.status_code == expected_status_code - class TestConnection(TestConnectionEndpoint): @mock.patch.dict(os.environ, {"AIRFLOW__CORE__TEST_CONNECTION": "Enabled"}) @@ -741,14 +663,3 @@ def test_should_respond_403_by_default(self): "Testing connections is disabled in Airflow configuration. " "Contact your deployment admin to enable it." ) - - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - @mock.patch.dict(os.environ, {"AIRFLOW__CORE__TEST_CONNECTION": "Enabled"}) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): - payload = {"connection_id": "test-connection-id", "conn_type": "sqlite"} - response = self.client.post("/api/v1/connections/test", json=payload) - assert response.status_code == expected_status_code diff --git a/tests/api_connexion/endpoints/test_dag_endpoint.py b/tests/api_connexion/endpoints/test_dag_endpoint.py index 4268d4ea1902..9905b4e27ab2 100644 --- a/tests/api_connexion/endpoints/test_dag_endpoint.py +++ b/tests/api_connexion/endpoints/test_dag_endpoint.py @@ -322,24 +322,6 @@ def test_should_respond_400_with_not_exists_fields(self, fields): ) assert response.status_code == 400, f"Current code: {response.status_code}" - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - dag_model = DagModel( - dag_id="TEST_DAG_1", - fileloc="/tmp/dag_1.py", - timetable_summary=None, - is_paused=False, - ) - session.add(dag_model) - session.commit() - - response = self.client.get("/api/v1/dags/TEST_DAG_1") - assert response.status_code == expected_status_code - class TestGetDagDetails(TestDagEndpoint): def test_should_respond_200(self, url_safe_serializer): @@ -739,18 +721,6 @@ def test_should_respond_400_with_not_exists_fields(self): ) assert response.status_code == 400, f"Current code: {response.status_code}" - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, url_safe_serializer): - self._create_dag_model_for_details_endpoint(self.dag_id) - url_safe_serializer.dumps("/tmp/dag.py") - response = self.client.get(f"/api/v1/dags/{self.dag_id}/details") - - assert response.status_code == expected_status_code - class TestGetDags(TestDagEndpoint): @provide_session @@ -1237,22 +1207,6 @@ def test_should_respond_400_with_not_exists_fields(self): assert response.status_code == 400, f"Current code: {response.status_code}" - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - self._create_dag_models(2) - self._create_deactivated_dag() - - dags_query = session.query(DagModel) - assert len(dags_query.all()) == 3 - - response = self.client.get("api/v1/dags") - - assert response.status_code == expected_status_code - class TestPatchDag(TestDagEndpoint): def test_should_respond_200_on_patch_is_paused(self, url_safe_serializer, session): @@ -1466,24 +1420,6 @@ def test_should_respond_403_unauthorized(self): assert response.status_code == 403 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set( - self, set_auto_role_public, expected_status_code, url_safe_serializer, session - ): - url_safe_serializer.dumps("/tmp/dag_1.py") - dag_model = self._create_dag_model() - payload = {"is_paused": False} - response = self.client.patch( - f"/api/v1/dags/{dag_model.dag_id}", - json=payload, - ) - - assert response.status_code == expected_status_code - class TestPatchDags(TestDagEndpoint): @provide_session @@ -2225,29 +2161,6 @@ def test_should_respons_400_dag_id_pattern_missing(self): ) assert response.status_code == 400 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set( - self, set_auto_role_public, expected_status_code, session, url_safe_serializer - ): - url_safe_serializer.dumps("/tmp/dag_1.py") - url_safe_serializer.dumps("/tmp/dag_2.py") - self._create_dag_models(2) - self._create_deactivated_dag() - - dags_query = session.query(DagModel) - assert len(dags_query.all()) == 3 - - response = self.client.patch( - "/api/v1/dags?dag_id_pattern=~", - json={"is_paused": False}, - ) - - assert response.status_code == expected_status_code - class TestDeleteDagEndpoint(TestDagEndpoint): def test_that_dag_can_be_deleted(self, session): @@ -2299,15 +2212,3 @@ def test_users_without_delete_permission_cannot_delete_dag(self): environ_overrides={"REMOTE_USER": "test_no_permissions"}, ) assert response.status_code == 403 - - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 204)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): - self._create_dag_models(1) - - response = self.client.delete("/api/v1/dags/TEST_DAG_1") - - assert response.status_code == expected_status_code diff --git a/tests/api_connexion/endpoints/test_dag_run_endpoint.py b/tests/api_connexion/endpoints/test_dag_run_endpoint.py index 31a356ce53a5..deb5fe0af2da 100644 --- a/tests/api_connexion/endpoints/test_dag_run_endpoint.py +++ b/tests/api_connexion/endpoints/test_dag_run_endpoint.py @@ -239,18 +239,6 @@ def test_should_raise_403_forbidden(self): ) assert response.status_code == 403 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 204)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - session.add_all(self._create_test_dag_run()) - session.commit() - response = self.client.delete("api/v1/dags/TEST_DAG_ID/dagRuns/TEST_DAG_RUN_ID_1") - - assert response.status_code == expected_status_code - class TestGetDagRun(TestDagRunEndpoint): def test_should_respond_200(self, session): @@ -374,29 +362,6 @@ def test_should_respond_400_with_not_exists_fields(self, session): ) assert response.status_code == 400, f"Current code: {response.status_code}" - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - dagrun_model = DagRun( - dag_id="TEST_DAG_ID", - run_id="TEST_DAG_RUN_ID", - run_type=DagRunType.MANUAL, - execution_date=timezone.parse(self.default_time), - start_date=timezone.parse(self.default_time), - external_trigger=True, - state="running", - ) - session.add(dagrun_model) - session.commit() - result = session.query(DagRun).all() - assert len(result) == 1 - - response = self.client.get("api/v1/dags/TEST_DAG_ID/dagRuns/TEST_DAG_RUN_ID") - assert response.status_code == expected_status_code - class TestGetDagRuns(TestDagRunEndpoint): def test_should_respond_200(self, session): @@ -581,18 +546,6 @@ def test_should_respond_400_with_not_exists_fields(self): ) assert response.status_code == 400, f"Current code: {response.status_code}" - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - self._create_test_dag_run() - result = session.query(DagRun).all() - assert len(result) == 2 - response = self.client.get("api/v1/dags/TEST_DAG_ID/dagRuns") - assert response.status_code == expected_status_code - class TestGetDagRunsPagination(TestDagRunEndpoint): @pytest.mark.parametrize( @@ -1032,18 +985,6 @@ def test_should_raises_401_unauthenticated(self): assert_401(response) - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): - self._create_test_dag_run() - - response = self.client.post("api/v1/dags/~/dagRuns/list", json={"dag_ids": ["TEST_DAG_ID"]}) - - assert response.status_code == expected_status_code - class TestGetDagRunBatchPagination(TestDagRunEndpoint): @pytest.mark.parametrize( @@ -1702,26 +1643,6 @@ def test_should_raises_403_unauthorized(self, username): ) assert response.status_code == 403 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): - execution_date = "2020-11-10T08:25:56.939143+00:00" - logical_date = "2020-11-10T08:25:56.939143+00:00" - self._create_dag("TEST_DAG_ID") - - response = self.client.post( - "api/v1/dags/TEST_DAG_ID/dagRuns", - json={ - "execution_date": execution_date, - "logical_date": logical_date, - }, - ) - - assert response.status_code == expected_status_code - class TestPatchDagRunState(TestDagRunEndpoint): @pytest.mark.parametrize("state", ["failed", "success", "queued"]) @@ -1848,31 +1769,6 @@ def test_should_respond_404(self): ) assert response.status_code == 404 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, dag_maker, session): - dag_id = "TEST_DAG_ID" - dag_run_id = "TEST_DAG_RUN_ID" - with dag_maker(dag_id) as dag: - task = EmptyOperator(task_id="task_id", dag=dag) - self.app.dag_bag.bag_dag(dag) - dr = dag_maker.create_dagrun(run_id=dag_run_id, run_type=DagRunType.SCHEDULED) - ti = dr.get_task_instance(task_id="task_id") - ti.task = task - ti.state = State.RUNNING - session.merge(ti) - session.commit() - - response = self.client.patch( - f"api/v1/dags/{dag_id}/dagRuns/{dag_run_id}", - json={"state": "failed"}, - ) - - assert response.status_code == expected_status_code - class TestClearDagRun(TestDagRunEndpoint): def test_should_respond_200(self, dag_maker, session): @@ -2011,31 +1907,6 @@ def test_should_respond_404(self): ) assert response.status_code == 404 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, dag_maker, session): - dag_id = "TEST_DAG_ID" - dag_run_id = "TEST_DAG_RUN_ID" - with dag_maker(dag_id) as dag: - task = EmptyOperator(task_id="task_id", dag=dag) - self.app.dag_bag.bag_dag(dag) - dr = dag_maker.create_dagrun(run_id=dag_run_id, run_type=DagRunType.SCHEDULED) - ti = dr.get_task_instance(task_id="task_id") - ti.task = task - ti.state = State.RUNNING - session.merge(ti) - session.commit() - - response = self.client.patch( - f"api/v1/dags/{dag_id}/dagRuns/{dag_run_id}", - json={"state": "failed"}, - ) - - assert response.status_code == expected_status_code - @pytest.mark.need_serialized_dag class TestGetDagRunDatasetTriggerEvents(TestDagRunEndpoint): @@ -2130,42 +2001,6 @@ def test_should_raises_401_unauthenticated(self, session): assert_401(response) - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, dag_maker, session): - dataset1 = Dataset(uri="ds1") - - with dag_maker(dag_id="source_dag", start_date=timezone.utcnow(), session=session): - EmptyOperator(task_id="task", outlets=[dataset1]) - dr = dag_maker.create_dagrun() - ti = dr.task_instances[0] - - ds1_id = session.query(DatasetModel.id).filter_by(uri=dataset1.uri).scalar() - event = DatasetEvent( - dataset_id=ds1_id, - source_task_id=ti.task_id, - source_dag_id=ti.dag_id, - source_run_id=ti.run_id, - source_map_index=ti.map_index, - ) - session.add(event) - - with dag_maker(dag_id="TEST_DAG_ID", start_date=timezone.utcnow(), session=session): - pass - dr = dag_maker.create_dagrun(run_id="TEST_DAG_RUN_ID", run_type=DagRunType.DATASET_TRIGGERED) - dr.consumed_dataset_events.append(event) - - session.commit() - assert event.timestamp - - response = self.client.get( - "api/v1/dags/TEST_DAG_ID/dagRuns/TEST_DAG_RUN_ID/upstreamDatasetEvents", - ) - assert response.status_code == expected_status_code - class TestSetDagRunNote(TestDagRunEndpoint): def test_should_respond_200(self, dag_maker, session): @@ -2282,23 +2117,3 @@ def test_should_respond_404(self): environ_overrides={"REMOTE_USER": "test"}, ) assert response.status_code == 404 - - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - dag_runs: list[DagRun] = self._create_test_dag_run(DagRunState.SUCCESS) - session.add_all(dag_runs) - session.commit() - created_dr: DagRun = dag_runs[0] - new_note_value = "My super cool DagRun notes" - response = self.client.patch( - f"api/v1/dags/{created_dr.dag_id}/dagRuns/{created_dr.run_id}/setNote", - json={"note": new_note_value}, - ) - - session.query(DagRun).filter(DagRun.run_id == created_dr.run_id).first() - - assert response.status_code == expected_status_code diff --git a/tests/api_connexion/endpoints/test_dag_source_endpoint.py b/tests/api_connexion/endpoints/test_dag_source_endpoint.py index ee3471b904bb..1e5389d37744 100644 --- a/tests/api_connexion/endpoints/test_dag_source_endpoint.py +++ b/tests/api_connexion/endpoints/test_dag_source_endpoint.py @@ -202,19 +202,3 @@ def test_should_respond_403_some_dags_not_readable_in_the_file(self, url_safe_se ) assert response.status_code == 403 assert read_dag.status_code == 200 - - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, url_safe_serializer): - dagbag = DagBag(dag_folder=EXAMPLE_DAG_FILE) - dagbag.sync_to_db() - test_dag: DAG = dagbag.dags[TEST_DAG_ID] - self._get_dag_file_docstring(test_dag.fileloc) - - url = f"/api/v1/dagSources/{url_safe_serializer.dumps(test_dag.fileloc)}" - response = self.client.get(url, headers={"Accept": "text/plain"}) - - assert response.status_code == expected_status_code diff --git a/tests/api_connexion/endpoints/test_dag_warning_endpoint.py b/tests/api_connexion/endpoints/test_dag_warning_endpoint.py index 5e0730ed13c5..3e7c805173b3 100644 --- a/tests/api_connexion/endpoints/test_dag_warning_endpoint.py +++ b/tests/api_connexion/endpoints/test_dag_warning_endpoint.py @@ -170,15 +170,3 @@ def test_should_raise_403_forbidden_when_user_has_no_dag_read_permission(self): query_string={"dag_id": "dag1"}, ) assert response.status_code == 403 - - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): - response = self.client.get( - "/api/v1/dagWarnings", - query_string={"dag_id": "dag1", "warning_type": "non-existent pool"}, - ) - assert response.status_code == expected_status_code diff --git a/tests/api_connexion/endpoints/test_dataset_endpoint.py b/tests/api_connexion/endpoints/test_dataset_endpoint.py index fb94b0ad9ff9..25f801203910 100644 --- a/tests/api_connexion/endpoints/test_dataset_endpoint.py +++ b/tests/api_connexion/endpoints/test_dataset_endpoint.py @@ -144,22 +144,6 @@ def test_should_raises_401_unauthenticated(self, session): response = self.client.get(f"/api/v1/datasets/{urllib.parse.quote('s3://bucket/key', safe='')}") assert_401(response) - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - self._create_dataset(session) - assert session.query(DatasetModel).count() == 1 - - with assert_queries_count(5): - response = self.client.get( - f"/api/v1/datasets/{urllib.parse.quote('s3://bucket/key', safe='')}", - ) - - assert response.status_code == expected_status_code - class TestGetDatasets(TestDatasetEndpoint): def test_should_respond_200(self, session): @@ -332,31 +316,6 @@ def test_filter_datasets_by_dag_ids_and_uri_pattern_works( response_data = response.json assert len(response_data["datasets"]) == expected_num - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - datasets = [ - DatasetModel( - id=i, - uri=f"s3://bucket/key/{i}", - extra={"foo": "bar"}, - created_at=timezone.parse(self.default_time), - updated_at=timezone.parse(self.default_time), - ) - for i in [1, 2] - ] - session.add_all(datasets) - session.commit() - assert session.query(DatasetModel).count() == 2 - - with assert_queries_count(8): - response = self.client.get("/api/v1/datasets") - - assert response.status_code == expected_status_code - class TestGetDatasetsEndpointPagination(TestDatasetEndpoint): @pytest.mark.parametrize( @@ -623,32 +582,6 @@ def test_includes_created_dagrun(self, session): "total_entries": 1, } - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - self._create_dataset(session) - common = { - "dataset_id": 1, - "extra": {"foo": "bar"}, - "source_dag_id": "foo", - "source_task_id": "bar", - "source_run_id": "custom", - "source_map_index": -1, - "created_dagruns": [], - } - - events = [DatasetEvent(id=i, timestamp=timezone.parse(self.default_time), **common) for i in [1, 2]] - session.add_all(events) - session.commit() - assert session.query(DatasetEvent).count() == 2 - - response = self.client.get("/api/v1/datasets/events") - - assert response.status_code == expected_status_code - class TestPostDatasetEvents(TestDatasetEndpoint): @pytest.fixture @@ -721,19 +654,6 @@ def test_should_raises_401_unauthenticated(self, session): response = self.client.post("/api/v1/datasets/events", json={"dataset_uri": "TEST_DATASET_URI"}) assert_401(response) - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - @pytest.mark.usefixtures("time_freezer") - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, session): - self._create_dataset(session) - event_payload = {"dataset_uri": "s3://bucket/key", "extra": {"foo": "bar"}} - response = self.client.post("/api/v1/datasets/events", json=event_payload) - - assert response.status_code == expected_status_code - class TestGetDatasetEventsEndpointPagination(TestDatasetEndpoint): @pytest.mark.parametrize( @@ -904,27 +824,6 @@ def test_should_raise_403_forbidden(self, session): assert response.status_code == 403 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - @pytest.mark.usefixtures("time_freezer") - def test_with_auth_role_public_set( - self, set_auto_role_public, expected_status_code, create_dummy_dag, session - ): - dag, _ = create_dummy_dag() - dag_id = dag.dag_id - dataset_id = self._create_dataset(session).id - self._create_dataset_dag_run_queues(dag_id, dataset_id, session) - dataset_uri = "s3://bucket/key" - - response = self.client.get( - f"/api/v1/dags/{dag_id}/datasets/queuedEvent/{dataset_uri}", - ) - - assert response.status_code == expected_status_code - class TestDeleteDagDatasetQueuedEvent(TestDatasetEndpoint): def test_delete_should_respond_204(self, session, create_dummy_dag): @@ -1042,24 +941,6 @@ def test_should_raise_403_forbidden(self): assert response.status_code == 403 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set( - self, set_auto_role_public, expected_status_code, session, create_dummy_dag - ): - dag, _ = create_dummy_dag() - dag_id = dag.dag_id - dataset_id = self._create_dataset(session).id - self._create_dataset_dag_run_queues(dag_id, dataset_id, session) - - response = self.client.get( - f"/api/v1/dags/{dag_id}/datasets/queuedEvent", - ) - assert response.status_code == expected_status_code - class TestDeleteDagDatasetQueuedEvents(TestDatasetEndpoint): def test_should_respond_404(self): @@ -1095,31 +976,6 @@ def test_should_raise_403_forbidden(self): assert response.status_code == 403 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 204)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set( - self, set_auto_role_public, expected_status_code, session, create_dummy_dag - ): - dag, _ = create_dummy_dag() - dag_id = dag.dag_id - dataset_uri = "s3://bucket/key" - dataset_id = self._create_dataset(session).id - - ddrq = DatasetDagRunQueue(target_dag_id=dag_id, dataset_id=dataset_id) - session.add(ddrq) - session.commit() - conn = session.query(DatasetDagRunQueue).all() - assert len(conn) == 1 - - response = self.client.delete( - f"/api/v1/dags/{dag_id}/datasets/queuedEvent/{dataset_uri}", - ) - - assert response.status_code == expected_status_code - class TestGetDatasetQueuedEvents(TestQueuedEventEndpoint): @pytest.mark.usefixtures("time_freezer") @@ -1180,26 +1036,6 @@ def test_should_raise_403_forbidden(self): assert response.status_code == 403 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - @pytest.mark.usefixtures("time_freezer") - def test_with_auth_role_public_set( - self, set_auto_role_public, expected_status_code, session, create_dummy_dag - ): - dag, _ = create_dummy_dag() - dag_id = dag.dag_id - dataset_id = self._create_dataset(session).id - self._create_dataset_dag_run_queues(dag_id, dataset_id, session) - - response = self.client.get( - f"/api/v1/dags/{dag_id}/datasets/queuedEvent", - ) - - assert response.status_code == expected_status_code - class TestDeleteDatasetQueuedEvents(TestQueuedEventEndpoint): def test_delete_should_respond_204(self, session, create_dummy_dag): @@ -1251,23 +1087,3 @@ def test_should_raise_403_forbidden(self): ) assert response.status_code == 403 - - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 204)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set( - self, set_auto_role_public, expected_status_code, session, create_dummy_dag - ): - dag, _ = create_dummy_dag() - dag_id = dag.dag_id - dataset_id = self._create_dataset(session).id - self._create_dataset_dag_run_queues(dag_id, dataset_id, session) - dataset_uri = "s3://bucket/key" - - response = self.client.delete( - f"/api/v1/datasets/queuedEvent/{dataset_uri}", - ) - - assert response.status_code == expected_status_code diff --git a/tests/api_connexion/endpoints/test_event_log_endpoint.py b/tests/api_connexion/endpoints/test_event_log_endpoint.py index e6f4d23ceae6..0fdef1a3af2b 100644 --- a/tests/api_connexion/endpoints/test_event_log_endpoint.py +++ b/tests/api_connexion/endpoints/test_event_log_endpoint.py @@ -109,21 +109,6 @@ def setup_attrs(self, configured_app) -> None: def teardown_method(self) -> None: clear_db_logs() - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, log_model): - event_log_id = log_model.id - response = self.client.get( - f"/api/v1/eventLogs/{event_log_id}", environ_overrides={"REMOTE_USER": "test"} - ) - - response = self.client.get("/api/v1/eventLogs") - - assert response.status_code == expected_status_code - class TestGetEventLog(TestEventLogEndpoint): def test_should_respond_200(self, log_model): @@ -171,18 +156,6 @@ def test_should_raise_403_forbidden(self): ) assert response.status_code == 403 - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code, log_model): - event_log_id = log_model.id - - response = self.client.get(f"/api/v1/eventLogs/{event_log_id}") - - assert response.status_code == expected_status_code - class TestGetEventLogs(TestEventLogEndpoint): def test_should_respond_200(self, session, create_log_model): @@ -392,23 +365,6 @@ def test_should_filter_eventlogs_by_excluded_events(self, create_log_model): assert response_data["total_entries"] == 1 assert {"cli_scheduler"} == {x["event"] for x in response_data["event_logs"]} - @pytest.mark.parametrize( - "set_auto_role_public, expected_status_code", - (("Public", 403), ("Admin", 200)), - indirect=["set_auto_role_public"], - ) - def test_with_auth_role_public_set( - self, set_auto_role_public, expected_status_code, create_log_model, session - ): - log_model_3 = Log(event="cli_scheduler", owner="root", extra='{"host_name": "e24b454f002a"}') - log_model_3.dttm = self.default_time_2 - - session.add(log_model_3) - session.flush() - response = self.client.get("/api/v1/eventLogs") - - assert response.status_code == expected_status_code - class TestGetEventLogPagination(TestEventLogEndpoint): @pytest.mark.parametrize( diff --git a/tests/api_connexion/endpoints/test_forward_to_fab_endpoint.py b/tests/api_connexion/endpoints/test_forward_to_fab_endpoint.py deleted file mode 100644 index 037e35914f74..000000000000 --- a/tests/api_connexion/endpoints/test_forward_to_fab_endpoint.py +++ /dev/null @@ -1,238 +0,0 @@ -# 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. -from __future__ import annotations - -from unittest import mock - -import pytest - -from airflow.auth.managers.base_auth_manager import BaseAuthManager -from airflow.providers.fab.auth_manager.models import Role, User -from airflow.security import permissions -from airflow.utils import timezone -from airflow.utils.session import create_session -from airflow.www.security_manager import EXISTING_ROLES -from tests.test_utils.api_connexion_utils import create_role, create_user, delete_role, delete_user - -pytestmark = [pytest.mark.db_test, pytest.mark.skip_if_database_isolation_mode] - -DEFAULT_TIME = "2020-06-11T18:00:00+00:00" - -EXAMPLE_USER_NAME = "example_user" - -EXAMPLE_USER_EMAIL = "example_user@example.com" - - -def _delete_user(**filters): - with create_session() as session: - user = session.query(User).filter_by(**filters).first() - if user is None: - return - user.roles = [] - session.delete(user) - - -@pytest.fixture -def autoclean_user_payload(autoclean_username, autoclean_email): - return { - "username": autoclean_username, - "password": "resutsop", - "email": autoclean_email, - "first_name": "Tester", - "last_name": "", - } - - -@pytest.fixture -def autoclean_admin_user(configured_app, autoclean_user_payload): - security_manager = configured_app.appbuilder.sm - return security_manager.add_user( - role=security_manager.find_role("Admin"), - **autoclean_user_payload, - ) - - -@pytest.fixture -def autoclean_username(): - _delete_user(username=EXAMPLE_USER_NAME) - yield EXAMPLE_USER_NAME - _delete_user(username=EXAMPLE_USER_NAME) - - -@pytest.fixture -def autoclean_email(): - _delete_user(email=EXAMPLE_USER_EMAIL) - yield EXAMPLE_USER_EMAIL - _delete_user(email=EXAMPLE_USER_EMAIL) - - -@pytest.fixture(scope="module") -def configured_app(minimal_app_for_api): - app = minimal_app_for_api - create_user( - app, # type: ignore - username="test", - role_name="Test", - permissions=[ - (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_ROLE), - (permissions.ACTION_CAN_READ, permissions.RESOURCE_ROLE), - (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_ROLE), - (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_ROLE), - (permissions.ACTION_CAN_READ, permissions.RESOURCE_ACTION), - (permissions.ACTION_CAN_CREATE, permissions.RESOURCE_USER), - (permissions.ACTION_CAN_DELETE, permissions.RESOURCE_USER), - (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_USER), - (permissions.ACTION_CAN_READ, permissions.RESOURCE_USER), - ], - ) - - yield app - - delete_user(app, username="test") # type: ignore - - -class TestFABforwarding: - @pytest.fixture(autouse=True) - def setup_attrs(self, configured_app) -> None: - self.app = configured_app - self.client = self.app.test_client() # type:ignore - - def teardown_method(self): - """ - Delete all roles except these ones. - Test and TestNoPermissions are deleted by delete_user above - """ - session = self.app.appbuilder.get_session - existing_roles = set(EXISTING_ROLES) - existing_roles.update(["Test", "TestNoPermissions"]) - roles = session.query(Role).filter(~Role.name.in_(existing_roles)).all() - for role in roles: - delete_role(self.app, role.name) - users = session.query(User).filter(User.changed_on == timezone.parse(DEFAULT_TIME)) - users.delete(synchronize_session=False) - session.commit() - - -class TestFABRoleForwarding(TestFABforwarding): - @mock.patch("airflow.api_connexion.endpoints.forward_to_fab_endpoint.get_auth_manager") - def test_raises_400_if_manager_is_not_fab(self, mock_get_auth_manager): - mock_get_auth_manager.return_value = BaseAuthManager(self.app.appbuilder) - response = self.client.get("api/v1/roles", environ_overrides={"REMOTE_USER": "test"}) - assert response.status_code == 400 - assert ( - response.json["detail"] - == "This endpoint is only available when using the default auth manager FabAuthManager." - ) - - def test_get_role_forwards_to_fab(self): - resp = self.client.get("api/v1/roles/Test", environ_overrides={"REMOTE_USER": "test"}) - assert resp.status_code == 200 - - def test_get_roles_forwards_to_fab(self): - resp = self.client.get("api/v1/roles", environ_overrides={"REMOTE_USER": "test"}) - assert resp.status_code == 200 - - def test_delete_role_forwards_to_fab(self): - role = create_role(self.app, "mytestrole") - resp = self.client.delete(f"api/v1/roles/{role.name}", environ_overrides={"REMOTE_USER": "test"}) - assert resp.status_code == 204 - - def test_patch_role_forwards_to_fab(self): - role = create_role(self.app, "mytestrole") - resp = self.client.patch( - f"api/v1/roles/{role.name}", json={"name": "Test2"}, environ_overrides={"REMOTE_USER": "test"} - ) - assert resp.status_code == 200 - - def test_post_role_forwards_to_fab(self): - payload = { - "name": "Test2", - "actions": [{"resource": {"name": "Connections"}, "action": {"name": "can_create"}}], - } - resp = self.client.post("api/v1/roles", json=payload, environ_overrides={"REMOTE_USER": "test"}) - assert resp.status_code == 200 - - def test_get_role_permissions_forwards_to_fab(self): - resp = self.client.get("api/v1/permissions", environ_overrides={"REMOTE_USER": "test"}) - assert resp.status_code == 200 - - -class TestFABUserForwarding(TestFABforwarding): - def _create_users(self, count, roles=None): - # create users with defined created_on and changed_on date - # for easy testing - if roles is None: - roles = [] - return [ - User( - first_name=f"test{i}", - last_name=f"test{i}", - username=f"TEST_USER{i}", - email=f"mytest@test{i}.org", - roles=roles or [], - created_on=timezone.parse(DEFAULT_TIME), - changed_on=timezone.parse(DEFAULT_TIME), - ) - for i in range(1, count + 1) - ] - - def test_get_user_forwards_to_fab(self): - users = self._create_users(1) - session = self.app.appbuilder.get_session - session.add_all(users) - session.commit() - resp = self.client.get("api/v1/users/TEST_USER1", environ_overrides={"REMOTE_USER": "test"}) - assert resp.status_code == 200 - - def test_get_users_forwards_to_fab(self): - users = self._create_users(2) - session = self.app.appbuilder.get_session - session.add_all(users) - session.commit() - resp = self.client.get("api/v1/users", environ_overrides={"REMOTE_USER": "test"}) - assert resp.status_code == 200 - - def test_post_user_forwards_to_fab(self, autoclean_username, autoclean_user_payload): - response = self.client.post( - "/api/v1/users", - json=autoclean_user_payload, - environ_overrides={"REMOTE_USER": "test"}, - ) - assert response.status_code == 200, response.json - - security_manager = self.app.appbuilder.sm - user = security_manager.find_user(autoclean_username) - assert user is not None - assert user.roles == [security_manager.find_role("Public")] - - @pytest.mark.usefixtures("autoclean_admin_user") - def test_patch_user_forwards_to_fab(self, autoclean_username, autoclean_user_payload): - autoclean_user_payload["first_name"] = "Changed" - response = self.client.patch( - f"/api/v1/users/{autoclean_username}", - json=autoclean_user_payload, - environ_overrides={"REMOTE_USER": "test"}, - ) - assert response.status_code == 200, response.json - - def test_delete_user_forwards_to_fab(self): - users = self._create_users(1) - session = self.app.appbuilder.get_session - session.add_all(users) - session.commit() - resp = self.client.delete("api/v1/users/TEST_USER1", environ_overrides={"REMOTE_USER": "test"}) - assert resp.status_code == 204 diff --git a/tests/providers/fab/auth_manager/api_endpoints/test_role_and_permission_endpoint.py b/tests/providers/fab/auth_manager/api_endpoints/test_role_and_permission_endpoint.py index a91a434412d9..77e3107a0b51 100644 --- a/tests/providers/fab/auth_manager/api_endpoints/test_role_and_permission_endpoint.py +++ b/tests/providers/fab/auth_manager/api_endpoints/test_role_and_permission_endpoint.py @@ -114,7 +114,7 @@ def test_should_raise_403_forbidden(self): ) def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): response = self.client.get("/auth/fab/v1/roles/Admin") - assert response.status_code == expected_status_code + assert response.status_code == expected_status_code, response.json class TestGetRolesEndpoint(TestRoleEndpoint): @@ -152,7 +152,7 @@ def test_should_raise_403_forbidden(self): ) def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): response = self.client.get("/auth/fab/v1/roles") - assert response.status_code == expected_status_code + assert response.status_code == expected_status_code, response.json class TestGetRolesEndpointPaginationandFilter(TestRoleEndpoint): @@ -214,7 +214,7 @@ def test_should_raise_403_forbidden(self): ) def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): response = self.client.get("/auth/fab/v1/permissions") - assert response.status_code == expected_status_code + assert response.status_code == expected_status_code, response.json class TestPostRole(TestRoleEndpoint): @@ -356,7 +356,7 @@ def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_c "actions": [{"resource": {"name": "Connections"}, "action": {"name": "can_create"}}], } response = self.client.post("/auth/fab/v1/roles", json=payload) - assert response.status_code == expected_status_code + assert response.status_code == expected_status_code, response.json class TestDeleteRole(TestRoleEndpoint): @@ -400,7 +400,7 @@ def test_should_raise_403_forbidden(self): def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_code): role = create_role(self.app, "mytestrole") response = self.client.delete(f"/auth/fab/v1/roles/{role.name}") - assert response.status_code == expected_status_code + assert response.status_code == expected_status_code, response.location class TestPatchRole(TestRoleEndpoint): @@ -589,4 +589,4 @@ def test_with_auth_role_public_set(self, set_auto_role_public, expected_status_c f"/auth/fab/v1/roles/{role.name}", json={"name": "mytest"}, ) - assert response.status_code == expected_status_code + assert response.status_code == expected_status_code, response.json diff --git a/tests/providers/fab/auth_manager/api_endpoints/test_user_endpoint.py b/tests/providers/fab/auth_manager/api_endpoints/test_user_endpoint.py index e83d9fcf8373..bc400c8a43fa 100644 --- a/tests/providers/fab/auth_manager/api_endpoints/test_user_endpoint.py +++ b/tests/providers/fab/auth_manager/api_endpoints/test_user_endpoint.py @@ -425,6 +425,7 @@ def autoclean_admin_user(configured_app, autoclean_user_payload): class TestPostUser(TestUserEndpoint): def test_with_default_role(self, autoclean_username, autoclean_user_payload): + self.client.application.config["AUTH_USER_REGISTRATION_ROLE"] = "Public" response = self.client.post( "/auth/fab/v1/users", json=autoclean_user_payload, diff --git a/tests/providers/fab/auth_manager/conftest.py b/tests/providers/fab/auth_manager/conftest.py index d478f895bb8f..da18f9d6c06b 100644 --- a/tests/providers/fab/auth_manager/conftest.py +++ b/tests/providers/fab/auth_manager/conftest.py @@ -34,7 +34,14 @@ def minimal_app_for_auth_api(): ] ) def factory(): - with conf_vars({("api", "auth_backends"): "tests.test_utils.remote_user_api_auth_backend"}): + with conf_vars( + { + ( + "api", + "auth_backends", + ): "tests.test_utils.remote_user_api_auth_backend,airflow.api.auth.backend.session" + } + ): _app = app.create_app(testing=True, config={"WTF_CSRF_ENABLED": False}) # type:ignore _app.config["AUTH_ROLE_PUBLIC"] = None return _app diff --git a/tests/providers/fab/auth_manager/test_fab_auth_manager.py b/tests/providers/fab/auth_manager/test_fab_auth_manager.py index 35e530be5a54..b755afcc70d0 100644 --- a/tests/providers/fab/auth_manager/test_fab_auth_manager.py +++ b/tests/providers/fab/auth_manager/test_fab_auth_manager.py @@ -120,22 +120,24 @@ def test_get_user(self, mock_current_user, auth_manager): assert auth_manager.get_user() == user + @pytest.mark.db_test @mock.patch.object(FabAuthManager, "get_user") - def test_is_logged_in(self, mock_get_user, auth_manager): + def test_is_logged_in(self, mock_get_user, auth_manager_with_appbuilder): user = Mock() user.is_anonymous.return_value = True mock_get_user.return_value = user - assert auth_manager.is_logged_in() is False + assert auth_manager_with_appbuilder.is_logged_in() is False + @pytest.mark.db_test @mock.patch.object(FabAuthManager, "get_user") - def test_is_logged_in_with_inactive_user(self, mock_get_user, auth_manager): + def test_is_logged_in_with_inactive_user(self, mock_get_user, auth_manager_with_appbuilder): user = Mock() user.is_anonymous.return_value = False user.is_active.return_value = True mock_get_user.return_value = user - assert auth_manager.is_logged_in() is False + assert auth_manager_with_appbuilder.is_logged_in() is False @pytest.mark.parametrize( "api_name, method, user_permissions, expected_result",