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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,54 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
get:
tags:
- FabAuthManager
summary: Get Role
description: Get an existing role.
operationId: get_role
security:
- OAuth2PasswordBearer: []
- HTTPBearer: []
parameters:
- name: name
in: path
required: true
schema:
type: string
minLength: 1
title: Name
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/RoleResponse'
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionResponse'
description: Unauthorized
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionResponse'
description: Forbidden
'404':
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionResponse'
description: Not Found
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
components:
schemas:
ActionResourceResponse:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,16 @@ def delete_role(name: str = Path(..., min_length=1)) -> None:
"""Delete an existing role."""
with get_application_builder():
return FABAuthManagerRoles.delete_role(name=name)


@roles_router.get(
"/roles/{name}",
responses=create_openapi_http_exception_doc(
[status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN, status.HTTP_404_NOT_FOUND]
),
dependencies=[Depends(requires_fab_custom_view("GET", permissions.RESOURCE_ROLE))],
)
def get_role(name: str = Path(..., min_length=1)) -> RoleResponse:
"""Get an existing role."""
with get_application_builder():
return FABAuthManagerRoles.get_role(name=name)
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,15 @@ def delete_role(cls, name: str) -> None:
detail=f"Role with name {name!r} does not exist.",
)
security_manager.delete_role(existing)

@classmethod
def get_role(cls, name: str) -> RoleResponse:
security_manager = get_fab_auth_manager().security_manager

existing = security_manager.find_role(name=name)
if not existing:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Role with name {name!r} does not exist.",
)
return RoleResponse.model_validate(existing)
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,83 @@ def test_delete_role_validation_404_empty_name(
resp = test_client.delete("/fab/v1/roles/")
assert resp.status_code == 404
mock_roles.delete_role.assert_not_called()

@patch("airflow.providers.fab.auth_manager.api_fastapi.routes.roles.FABAuthManagerRoles")
@patch("airflow.providers.fab.auth_manager.api_fastapi.security.get_auth_manager")
@patch(
"airflow.providers.fab.auth_manager.api_fastapi.routes.roles.get_application_builder",
return_value=_noop_cm(),
)
def test_get_role(
self, mock_get_application_builder, mock_get_auth_manager, mock_roles, test_client, as_user
):
mgr = MagicMock()
mgr.is_authorized_custom_view.return_value = True
mock_get_auth_manager.return_value = mgr

dummy_out = RoleResponse(name="roleA", permissions=[])
mock_roles.get_role.return_value = dummy_out

with as_user():
resp = test_client.get("/fab/v1/roles/roleA")
assert resp.status_code == 200
assert resp.json() == dummy_out.model_dump(by_alias=True)
mock_roles.get_role.assert_called_once_with(name="roleA")

@patch("airflow.providers.fab.auth_manager.api_fastapi.routes.roles.FABAuthManagerRoles")
@patch("airflow.providers.fab.auth_manager.api_fastapi.security.get_auth_manager")
@patch(
"airflow.providers.fab.auth_manager.api_fastapi.routes.roles.get_application_builder",
return_value=_noop_cm(),
)
def test_get_role_forbidden(
self, mock_get_application_builder, mock_get_auth_manager, mock_roles, test_client, as_user
):
mgr = MagicMock()
mgr.is_authorized_custom_view.return_value = False
mock_get_auth_manager.return_value = mgr

with as_user():
resp = test_client.get("/fab/v1/roles/roleA")
assert resp.status_code == 403
mock_roles.get_role.assert_not_called()

@patch("airflow.providers.fab.auth_manager.api_fastapi.routes.roles.FABAuthManagerRoles")
@patch("airflow.providers.fab.auth_manager.api_fastapi.security.get_auth_manager")
@patch(
"airflow.providers.fab.auth_manager.api_fastapi.routes.roles.get_application_builder",
return_value=_noop_cm(),
)
def test_get_role_validation_404_not_found(
self, mock_get_application_builder, mock_get_auth_manager, mock_roles, test_client, as_user
):
mgr = MagicMock()
mgr.is_authorized_custom_view.return_value = True
mock_get_auth_manager.return_value = mgr
mock_roles.get_role.side_effect = HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Role with name 'non_existent_role' does not exist.",
)

with as_user():
resp = test_client.get("/fab/v1/roles/non_existent_role")
assert resp.status_code == 404
mock_roles.get_role.assert_called_once_with(name="non_existent_role")

@patch("airflow.providers.fab.auth_manager.api_fastapi.routes.roles.FABAuthManagerRoles")
@patch("airflow.providers.fab.auth_manager.api_fastapi.security.get_auth_manager")
@patch(
"airflow.providers.fab.auth_manager.api_fastapi.routes.roles.get_application_builder",
return_value=_noop_cm(),
)
def test_get_role_validation_404_empty_name(
self, mock_get_application_builder, mock_get_auth_manager, mock_roles, test_client, as_user
):
mgr = MagicMock()
mgr.is_authorized_custom_view.return_value = True
mock_get_auth_manager.return_value = mgr

with as_user():
resp = test_client.get("/fab/v1/roles/")
assert resp.status_code == 404
mock_roles.get_role.assert_not_called()
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,26 @@ def test_delete_role_not_found(self, get_fab_auth_manager, fab_auth_manager, sec
with pytest.raises(HTTPException) as ex:
FABAuthManagerRoles.delete_role(name="roleA")
assert ex.value.status_code == 404

# GET /roles/{name}

def test_get_role_success(self, get_fab_auth_manager, fab_auth_manager, security_manager):
security_manager.find_role.return_value = _make_role_obj("roleA", [("can_read", "DAG")])
fab_auth_manager.security_manager = security_manager
get_fab_auth_manager.return_value = fab_auth_manager

out = FABAuthManagerRoles.get_role(name="roleA")

assert out.name == "roleA"
assert out.permissions
assert out.permissions[0].action.name == "can_read"
assert out.permissions[0].resource.name == "DAG"

def test_get_role_not_found(self, get_fab_auth_manager, fab_auth_manager, security_manager):
security_manager.find_role.return_value = None
fab_auth_manager.security_manager = security_manager
get_fab_auth_manager.return_value = fab_auth_manager

with pytest.raises(HTTPException) as ex:
FABAuthManagerRoles.get_role(name="roleA")
assert ex.value.status_code == 404