Skip to content
Closed
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 @@ -40,6 +40,12 @@ class ActionResourceResponse(BaseModel):
resource: ResourceResponse


class Role(BaseModel):
"""Lightweight role reference used by /users schemas."""

name: str


class RoleBody(StrictBaseModel):
"""Incoming payload for creating/updating a role."""

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# 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 datetime import datetime, timezone
from typing import TYPE_CHECKING

from pydantic import Field, SecretStr, field_validator

from airflow.api_fastapi.core_api.base import BaseModel, StrictBaseModel
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.roles import Role

if TYPE_CHECKING:
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.roles import Role
Comment on lines +27 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we don't need the TYPE_CHECKING as we already imported in line 25.

Suggested change
if TYPE_CHECKING:
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.roles import Role



class UserBody(StrictBaseModel):
"""Incoming payload for creating a user."""

username: str = Field(min_length=1)
email: str = Field(min_length=1)
first_name: str = Field(min_length=1)
last_name: str = Field(min_length=1)
roles: list[Role] | None = None
password: SecretStr


class UserResponse(BaseModel):
"""Outgoing representation of a user (no password)."""

username: str
email: str
first_name: str
last_name: str
roles: list[Role] | None = None
active: bool | None = None
last_login: datetime | None = None
login_count: int | None = None
fail_login_count: int | None = None
created_on: datetime | None = None
changed_on: datetime | None = None

@field_validator("created_on", "changed_on")
@classmethod
def _coerce_tzaware(cls, v: datetime | None) -> datetime | None:
if v is None:
return None
return v if (v.tzinfo and v.utcoffset() is not None) else v.replace(tzinfo=timezone.utc)
Comment on lines +54 to +62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to leverage UtcDateTime ? Then we don't need to handle the common timezone validation for this case.

Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,64 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/auth/fab/v1/users:
post:
tags:
- FabAuthManager
summary: Create User
operationId: create_user
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserBody'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/UserResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionResponse'
'409':
description: Conflict
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionResponse'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPExceptionResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security:
- OAuth2PasswordBearer: []
- HTTPBearer: []
components:
schemas:
ActionResourceResponse:
Expand Down Expand Up @@ -404,6 +462,16 @@ components:
- name
title: ResourceResponse
description: Outgoing representation of a resource.
Role:
properties:
name:
type: string
title: Name
type: object
required:
- name
title: Role
description: Lightweight role reference used by /users schemas.
RoleBody:
properties:
name:
Expand Down Expand Up @@ -452,6 +520,108 @@ components:
- name
title: RoleResponse
description: Outgoing representation of a role and its permissions.
UserBody:
properties:
username:
type: string
minLength: 1
title: Username
email:
type: string
minLength: 1
title: Email
first_name:
type: string
minLength: 1
title: First Name
last_name:
type: string
minLength: 1
title: Last Name
roles:
anyOf:
- items:
$ref: '#/components/schemas/Role'
type: array
- type: 'null'
title: Roles
password:
type: string
format: password
title: Password
writeOnly: true
additionalProperties: false
type: object
required:
- username
- email
- first_name
- last_name
- password
title: UserBody
description: Incoming payload for creating a user.
UserResponse:
properties:
username:
type: string
title: Username
email:
type: string
title: Email
first_name:
type: string
title: First Name
last_name:
type: string
title: Last Name
roles:
anyOf:
- items:
$ref: '#/components/schemas/Role'
type: array
- type: 'null'
title: Roles
active:
anyOf:
- type: boolean
- type: 'null'
title: Active
last_login:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Last Login
login_count:
anyOf:
- type: integer
- type: 'null'
title: Login Count
fail_login_count:
anyOf:
- type: integer
- type: 'null'
title: Fail Login Count
created_on:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Created On
changed_on:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Changed On
type: object
required:
- username
- email
- first_name
- last_name
title: UserResponse
description: Outgoing representation of a user (no password).
ValidationError:
properties:
loc:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 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 typing import TYPE_CHECKING

from fastapi import Depends, status

from airflow.api_fastapi.common.router import AirflowRouter
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.users import UserBody, UserResponse
from airflow.providers.fab.auth_manager.api_fastapi.security import requires_fab_custom_view
from airflow.providers.fab.auth_manager.api_fastapi.services.users import FABAuthManagerUsers
from airflow.providers.fab.auth_manager.cli_commands.utils import get_application_builder
from airflow.providers.fab.www.security import permissions

if TYPE_CHECKING:
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.users import UserBody, UserResponse
Comment on lines +31 to +32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if TYPE_CHECKING:
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.users import UserBody, UserResponse


users_router = AirflowRouter(prefix="/fab/v1", tags=["FabAuthManager"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking nit, can be done in follow-up PR:
How about adding a common root router for all providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/routes/<entity>.py? Then we could specified the prefix and tags in single source.



@users_router.post(
"/users",
responses=create_openapi_http_exception_doc(
[
status.HTTP_400_BAD_REQUEST,
status.HTTP_401_UNAUTHORIZED,
status.HTTP_403_FORBIDDEN,
status.HTTP_409_CONFLICT,
status.HTTP_500_INTERNAL_SERVER_ERROR,
]
),
dependencies=[Depends(requires_fab_custom_view("POST", permissions.RESOURCE_USER))],
)
def create_user(body: UserBody) -> UserResponse:
with get_application_builder():
return FABAuthManagerUsers.create_user(body=body)
Loading
Loading