apache-airflow-providers-fab 3.0.1__py3-none-any.whl → 3.1.1rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airflow/providers/fab/__init__.py +1 -1
- airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +2 -2
- airflow/providers/fab/auth_manager/api_fastapi/datamodels/login.py +0 -7
- airflow/providers/fab/auth_manager/api_fastapi/datamodels/roles.py +63 -0
- airflow/providers/fab/auth_manager/api_fastapi/openapi/v2-fab-auth-manager-generated.yaml +416 -16
- airflow/providers/fab/auth_manager/api_fastapi/parameters.py +55 -0
- airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +37 -5
- airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py +137 -0
- airflow/providers/fab/auth_manager/api_fastapi/security.py +32 -0
- airflow/providers/fab/auth_manager/api_fastapi/services/login.py +12 -25
- airflow/providers/fab/auth_manager/api_fastapi/services/roles.py +158 -0
- airflow/providers/fab/auth_manager/api_fastapi/sorting.py +49 -0
- airflow/providers/fab/auth_manager/cli_commands/permissions_command.py +6 -2
- airflow/providers/fab/auth_manager/fab_auth_manager.py +33 -3
- airflow/providers/fab/auth_manager/models/__init__.py +3 -8
- airflow/providers/fab/auth_manager/models/db.py +1 -1
- airflow/providers/fab/auth_manager/security_manager/override.py +60 -17
- airflow/providers/fab/version_compat.py +1 -0
- airflow/providers/fab/www/api_connexion/parameters.py +1 -46
- airflow/providers/fab/www/app.py +13 -10
- airflow/providers/fab/www/extensions/init_appbuilder.py +5 -2
- airflow/providers/fab/www/extensions/init_security.py +1 -1
- airflow/providers/fab/www/extensions/init_views.py +11 -7
- airflow/providers/fab/www/package-lock.json +417 -265
- airflow/providers/fab/www/package.json +13 -10
- airflow/providers/fab/www/session.py +5 -8
- airflow/providers/fab/www/static/dist/{743.935ed3d26e56ed8f63d3.js → 743.0c0bf201ae17e66a9a3f.js} +1 -1
- airflow/providers/fab/www/static/dist/{main.3cf3be1a0c5439bb640d.js → main.bc1f701c3d133e2a3bab.js} +1 -1
- airflow/providers/fab/www/static/dist/manifest.json +13 -13
- airflow/providers/fab/www/views.py +18 -14
- {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/METADATA +15 -14
- {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/RECORD +51 -45
- /airflow/providers/fab/migrations/versions/{0001_1_4_0_create_ab_tables_if_missing.py → 0000_1_4_0_create_ab_tables_if_missing.py} +0 -0
- /airflow/providers/fab/www/static/dist/{743.935ed3d26e56ed8f63d3.js.LICENSE.txt → 743.0c0bf201ae17e66a9a3f.js.LICENSE.txt} +0 -0
- /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.ff5a35f322070b094aa2.css → airflowDefaultTheme.ef6fc04c9b6920cd75c9.css} +0 -0
- /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.ff5a35f322070b094aa2.js → airflowDefaultTheme.ef6fc04c9b6920cd75c9.js} +0 -0
- /airflow/providers/fab/www/static/dist/{flash.5583a9e0cf11f2be93da.css → flash.eaaf777ec1b3628cf7be.css} +0 -0
- /airflow/providers/fab/www/static/dist/{flash.5583a9e0cf11f2be93da.js → flash.eaaf777ec1b3628cf7be.js} +0 -0
- /airflow/providers/fab/www/static/dist/{loadingDots.2e5f555f0753107b0300.css → loadingDots.76f4332c0a932c3dc08f.css} +0 -0
- /airflow/providers/fab/www/static/dist/{loadingDots.2e5f555f0753107b0300.js → loadingDots.76f4332c0a932c3dc08f.js} +0 -0
- /airflow/providers/fab/www/static/dist/{main.3cf3be1a0c5439bb640d.css → main.bc1f701c3d133e2a3bab.css} +0 -0
- /airflow/providers/fab/www/static/dist/{main.3cf3be1a0c5439bb640d.js.LICENSE.txt → main.bc1f701c3d133e2a3bab.js.LICENSE.txt} +0 -0
- /airflow/providers/fab/www/static/dist/{materialIcons.3e67dd6fbfcc4f3b5105.css → materialIcons.ad07a489b2f0fc1a96bf.css} +0 -0
- /airflow/providers/fab/www/static/dist/{materialIcons.3e67dd6fbfcc4f3b5105.js → materialIcons.ad07a489b2f0fc1a96bf.js} +0 -0
- /airflow/providers/fab/www/static/dist/{moment.9baee5ec3d7639a10897.js → moment.5b85b4f6be2fe9c405ac.js} +0 -0
- /airflow/providers/fab/www/static/dist/{runtime.6ad9da077ea169d60db9.js → runtime.254c277d91ce3ac79c64.js} +0 -0
- {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/entry_points.txt +0 -0
- {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/licenses/3rd-party-licenses/LICENSES-ui.txt +0 -0
- {airflow/providers/fab → apache_airflow_providers_fab-3.1.1rc1.dist-info/licenses}/LICENSE +0 -0
- {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/licenses/NOTICE +0 -0
|
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
|
|
|
29
29
|
|
|
30
30
|
__all__ = ["__version__"]
|
|
31
31
|
|
|
32
|
-
__version__ = "3.
|
|
32
|
+
__version__ = "3.1.1"
|
|
33
33
|
|
|
34
34
|
if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
|
|
35
35
|
"3.0.2"
|
|
@@ -162,8 +162,8 @@ def patch_user(*, username: str, update_mask: UpdateMask = None) -> APIResponse:
|
|
|
162
162
|
if update_mask is not None:
|
|
163
163
|
masked_data = {}
|
|
164
164
|
missing_mask_names = []
|
|
165
|
-
for
|
|
166
|
-
field =
|
|
165
|
+
for field_raw in update_mask:
|
|
166
|
+
field = field_raw.strip()
|
|
167
167
|
try:
|
|
168
168
|
masked_data[field] = data[field]
|
|
169
169
|
except KeyError:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
|
3
|
+
# distributed with this work for additional information
|
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
|
6
|
+
# "License"); you may not use this file except in compliance
|
|
7
|
+
# with the License. You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from pydantic import Field
|
|
20
|
+
|
|
21
|
+
from airflow.api_fastapi.core_api.base import BaseModel, StrictBaseModel
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ActionResponse(BaseModel):
|
|
25
|
+
"""Outgoing representation of an action (permission name)."""
|
|
26
|
+
|
|
27
|
+
name: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ResourceResponse(BaseModel):
|
|
31
|
+
"""Outgoing representation of a resource."""
|
|
32
|
+
|
|
33
|
+
name: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ActionResourceResponse(BaseModel):
|
|
37
|
+
"""Pairing of an action with a resource."""
|
|
38
|
+
|
|
39
|
+
action: ActionResponse
|
|
40
|
+
resource: ResourceResponse
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RoleBody(StrictBaseModel):
|
|
44
|
+
"""Incoming payload for creating/updating a role."""
|
|
45
|
+
|
|
46
|
+
name: str = Field(min_length=1)
|
|
47
|
+
permissions: list[ActionResourceResponse] = Field(
|
|
48
|
+
default_factory=list, alias="actions", validation_alias="actions"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RoleResponse(BaseModel):
|
|
53
|
+
"""Outgoing representation of a role and its permissions."""
|
|
54
|
+
|
|
55
|
+
name: str
|
|
56
|
+
permissions: list[ActionResourceResponse] = Field(default_factory=list, serialization_alias="actions")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class RoleCollectionResponse(BaseModel):
|
|
60
|
+
"""Outgoing representation of a paginated collection of roles."""
|
|
61
|
+
|
|
62
|
+
roles: list[RoleResponse]
|
|
63
|
+
total_entries: int
|
|
@@ -17,7 +17,9 @@ paths:
|
|
|
17
17
|
content:
|
|
18
18
|
application/json:
|
|
19
19
|
schema:
|
|
20
|
-
|
|
20
|
+
additionalProperties: true
|
|
21
|
+
type: object
|
|
22
|
+
title: Body
|
|
21
23
|
required: true
|
|
22
24
|
responses:
|
|
23
25
|
'201':
|
|
@@ -55,7 +57,9 @@ paths:
|
|
|
55
57
|
content:
|
|
56
58
|
application/json:
|
|
57
59
|
schema:
|
|
58
|
-
|
|
60
|
+
additionalProperties: true
|
|
61
|
+
type: object
|
|
62
|
+
title: Body
|
|
59
63
|
required: true
|
|
60
64
|
responses:
|
|
61
65
|
'201':
|
|
@@ -82,8 +86,342 @@ paths:
|
|
|
82
86
|
application/json:
|
|
83
87
|
schema:
|
|
84
88
|
$ref: '#/components/schemas/HTTPValidationError'
|
|
89
|
+
/auth/logout:
|
|
90
|
+
get:
|
|
91
|
+
tags:
|
|
92
|
+
- FabAuthManager
|
|
93
|
+
summary: Logout
|
|
94
|
+
description: Generate a new API token.
|
|
95
|
+
operationId: logout
|
|
96
|
+
responses:
|
|
97
|
+
'307':
|
|
98
|
+
description: Successful Response
|
|
99
|
+
content:
|
|
100
|
+
application/json:
|
|
101
|
+
schema: {}
|
|
102
|
+
/auth/fab/v1/roles:
|
|
103
|
+
post:
|
|
104
|
+
tags:
|
|
105
|
+
- FabAuthManager
|
|
106
|
+
summary: Create Role
|
|
107
|
+
description: Create a new role (actions can be empty).
|
|
108
|
+
operationId: create_role
|
|
109
|
+
security:
|
|
110
|
+
- OAuth2PasswordBearer: []
|
|
111
|
+
- HTTPBearer: []
|
|
112
|
+
requestBody:
|
|
113
|
+
required: true
|
|
114
|
+
content:
|
|
115
|
+
application/json:
|
|
116
|
+
schema:
|
|
117
|
+
$ref: '#/components/schemas/RoleBody'
|
|
118
|
+
responses:
|
|
119
|
+
'200':
|
|
120
|
+
description: Successful Response
|
|
121
|
+
content:
|
|
122
|
+
application/json:
|
|
123
|
+
schema:
|
|
124
|
+
$ref: '#/components/schemas/RoleResponse'
|
|
125
|
+
'400':
|
|
126
|
+
content:
|
|
127
|
+
application/json:
|
|
128
|
+
schema:
|
|
129
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
130
|
+
description: Bad Request
|
|
131
|
+
'401':
|
|
132
|
+
content:
|
|
133
|
+
application/json:
|
|
134
|
+
schema:
|
|
135
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
136
|
+
description: Unauthorized
|
|
137
|
+
'403':
|
|
138
|
+
content:
|
|
139
|
+
application/json:
|
|
140
|
+
schema:
|
|
141
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
142
|
+
description: Forbidden
|
|
143
|
+
'409':
|
|
144
|
+
content:
|
|
145
|
+
application/json:
|
|
146
|
+
schema:
|
|
147
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
148
|
+
description: Conflict
|
|
149
|
+
'500':
|
|
150
|
+
content:
|
|
151
|
+
application/json:
|
|
152
|
+
schema:
|
|
153
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
154
|
+
description: Internal Server Error
|
|
155
|
+
'422':
|
|
156
|
+
description: Validation Error
|
|
157
|
+
content:
|
|
158
|
+
application/json:
|
|
159
|
+
schema:
|
|
160
|
+
$ref: '#/components/schemas/HTTPValidationError'
|
|
161
|
+
get:
|
|
162
|
+
tags:
|
|
163
|
+
- FabAuthManager
|
|
164
|
+
summary: Get Roles
|
|
165
|
+
description: List roles with pagination and ordering.
|
|
166
|
+
operationId: get_roles
|
|
167
|
+
security:
|
|
168
|
+
- OAuth2PasswordBearer: []
|
|
169
|
+
- HTTPBearer: []
|
|
170
|
+
parameters:
|
|
171
|
+
- name: order_by
|
|
172
|
+
in: query
|
|
173
|
+
required: false
|
|
174
|
+
schema:
|
|
175
|
+
type: string
|
|
176
|
+
description: Field to order by. Prefix with '-' for descending.
|
|
177
|
+
default: name
|
|
178
|
+
title: Order By
|
|
179
|
+
description: Field to order by. Prefix with '-' for descending.
|
|
180
|
+
- name: offset
|
|
181
|
+
in: query
|
|
182
|
+
required: false
|
|
183
|
+
schema:
|
|
184
|
+
type: integer
|
|
185
|
+
minimum: 0
|
|
186
|
+
description: Number of items to skip before starting to collect results.
|
|
187
|
+
default: 0
|
|
188
|
+
title: Offset
|
|
189
|
+
description: Number of items to skip before starting to collect results.
|
|
190
|
+
- name: limit
|
|
191
|
+
in: query
|
|
192
|
+
required: false
|
|
193
|
+
schema:
|
|
194
|
+
type: integer
|
|
195
|
+
minimum: 0
|
|
196
|
+
default: 100
|
|
197
|
+
title: Limit
|
|
198
|
+
responses:
|
|
199
|
+
'200':
|
|
200
|
+
description: Successful Response
|
|
201
|
+
content:
|
|
202
|
+
application/json:
|
|
203
|
+
schema:
|
|
204
|
+
$ref: '#/components/schemas/RoleCollectionResponse'
|
|
205
|
+
'400':
|
|
206
|
+
content:
|
|
207
|
+
application/json:
|
|
208
|
+
schema:
|
|
209
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
210
|
+
description: Bad Request
|
|
211
|
+
'401':
|
|
212
|
+
content:
|
|
213
|
+
application/json:
|
|
214
|
+
schema:
|
|
215
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
216
|
+
description: Unauthorized
|
|
217
|
+
'403':
|
|
218
|
+
content:
|
|
219
|
+
application/json:
|
|
220
|
+
schema:
|
|
221
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
222
|
+
description: Forbidden
|
|
223
|
+
'500':
|
|
224
|
+
content:
|
|
225
|
+
application/json:
|
|
226
|
+
schema:
|
|
227
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
228
|
+
description: Internal Server Error
|
|
229
|
+
'422':
|
|
230
|
+
description: Validation Error
|
|
231
|
+
content:
|
|
232
|
+
application/json:
|
|
233
|
+
schema:
|
|
234
|
+
$ref: '#/components/schemas/HTTPValidationError'
|
|
235
|
+
/auth/fab/v1/roles/{name}:
|
|
236
|
+
delete:
|
|
237
|
+
tags:
|
|
238
|
+
- FabAuthManager
|
|
239
|
+
summary: Delete Role
|
|
240
|
+
description: Delete an existing role.
|
|
241
|
+
operationId: delete_role
|
|
242
|
+
security:
|
|
243
|
+
- OAuth2PasswordBearer: []
|
|
244
|
+
- HTTPBearer: []
|
|
245
|
+
parameters:
|
|
246
|
+
- name: name
|
|
247
|
+
in: path
|
|
248
|
+
required: true
|
|
249
|
+
schema:
|
|
250
|
+
type: string
|
|
251
|
+
minLength: 1
|
|
252
|
+
title: Name
|
|
253
|
+
responses:
|
|
254
|
+
'200':
|
|
255
|
+
description: Successful Response
|
|
256
|
+
content:
|
|
257
|
+
application/json:
|
|
258
|
+
schema: {}
|
|
259
|
+
'401':
|
|
260
|
+
content:
|
|
261
|
+
application/json:
|
|
262
|
+
schema:
|
|
263
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
264
|
+
description: Unauthorized
|
|
265
|
+
'403':
|
|
266
|
+
content:
|
|
267
|
+
application/json:
|
|
268
|
+
schema:
|
|
269
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
270
|
+
description: Forbidden
|
|
271
|
+
'404':
|
|
272
|
+
content:
|
|
273
|
+
application/json:
|
|
274
|
+
schema:
|
|
275
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
276
|
+
description: Not Found
|
|
277
|
+
'422':
|
|
278
|
+
description: Validation Error
|
|
279
|
+
content:
|
|
280
|
+
application/json:
|
|
281
|
+
schema:
|
|
282
|
+
$ref: '#/components/schemas/HTTPValidationError'
|
|
283
|
+
get:
|
|
284
|
+
tags:
|
|
285
|
+
- FabAuthManager
|
|
286
|
+
summary: Get Role
|
|
287
|
+
description: Get an existing role.
|
|
288
|
+
operationId: get_role
|
|
289
|
+
security:
|
|
290
|
+
- OAuth2PasswordBearer: []
|
|
291
|
+
- HTTPBearer: []
|
|
292
|
+
parameters:
|
|
293
|
+
- name: name
|
|
294
|
+
in: path
|
|
295
|
+
required: true
|
|
296
|
+
schema:
|
|
297
|
+
type: string
|
|
298
|
+
minLength: 1
|
|
299
|
+
title: Name
|
|
300
|
+
responses:
|
|
301
|
+
'200':
|
|
302
|
+
description: Successful Response
|
|
303
|
+
content:
|
|
304
|
+
application/json:
|
|
305
|
+
schema:
|
|
306
|
+
$ref: '#/components/schemas/RoleResponse'
|
|
307
|
+
'401':
|
|
308
|
+
content:
|
|
309
|
+
application/json:
|
|
310
|
+
schema:
|
|
311
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
312
|
+
description: Unauthorized
|
|
313
|
+
'403':
|
|
314
|
+
content:
|
|
315
|
+
application/json:
|
|
316
|
+
schema:
|
|
317
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
318
|
+
description: Forbidden
|
|
319
|
+
'404':
|
|
320
|
+
content:
|
|
321
|
+
application/json:
|
|
322
|
+
schema:
|
|
323
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
324
|
+
description: Not Found
|
|
325
|
+
'422':
|
|
326
|
+
description: Validation Error
|
|
327
|
+
content:
|
|
328
|
+
application/json:
|
|
329
|
+
schema:
|
|
330
|
+
$ref: '#/components/schemas/HTTPValidationError'
|
|
331
|
+
patch:
|
|
332
|
+
tags:
|
|
333
|
+
- FabAuthManager
|
|
334
|
+
summary: Patch Role
|
|
335
|
+
description: Update an existing role.
|
|
336
|
+
operationId: patch_role
|
|
337
|
+
security:
|
|
338
|
+
- OAuth2PasswordBearer: []
|
|
339
|
+
- HTTPBearer: []
|
|
340
|
+
parameters:
|
|
341
|
+
- name: name
|
|
342
|
+
in: path
|
|
343
|
+
required: true
|
|
344
|
+
schema:
|
|
345
|
+
type: string
|
|
346
|
+
minLength: 1
|
|
347
|
+
title: Name
|
|
348
|
+
- name: update_mask
|
|
349
|
+
in: query
|
|
350
|
+
required: false
|
|
351
|
+
schema:
|
|
352
|
+
anyOf:
|
|
353
|
+
- type: string
|
|
354
|
+
- type: 'null'
|
|
355
|
+
description: Comma-separated list of fields to update
|
|
356
|
+
title: Update Mask
|
|
357
|
+
description: Comma-separated list of fields to update
|
|
358
|
+
requestBody:
|
|
359
|
+
required: true
|
|
360
|
+
content:
|
|
361
|
+
application/json:
|
|
362
|
+
schema:
|
|
363
|
+
$ref: '#/components/schemas/RoleBody'
|
|
364
|
+
responses:
|
|
365
|
+
'200':
|
|
366
|
+
description: Successful Response
|
|
367
|
+
content:
|
|
368
|
+
application/json:
|
|
369
|
+
schema:
|
|
370
|
+
$ref: '#/components/schemas/RoleResponse'
|
|
371
|
+
'400':
|
|
372
|
+
content:
|
|
373
|
+
application/json:
|
|
374
|
+
schema:
|
|
375
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
376
|
+
description: Bad Request
|
|
377
|
+
'401':
|
|
378
|
+
content:
|
|
379
|
+
application/json:
|
|
380
|
+
schema:
|
|
381
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
382
|
+
description: Unauthorized
|
|
383
|
+
'403':
|
|
384
|
+
content:
|
|
385
|
+
application/json:
|
|
386
|
+
schema:
|
|
387
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
388
|
+
description: Forbidden
|
|
389
|
+
'404':
|
|
390
|
+
content:
|
|
391
|
+
application/json:
|
|
392
|
+
schema:
|
|
393
|
+
$ref: '#/components/schemas/HTTPExceptionResponse'
|
|
394
|
+
description: Not Found
|
|
395
|
+
'422':
|
|
396
|
+
description: Validation Error
|
|
397
|
+
content:
|
|
398
|
+
application/json:
|
|
399
|
+
schema:
|
|
400
|
+
$ref: '#/components/schemas/HTTPValidationError'
|
|
85
401
|
components:
|
|
86
402
|
schemas:
|
|
403
|
+
ActionResourceResponse:
|
|
404
|
+
properties:
|
|
405
|
+
action:
|
|
406
|
+
$ref: '#/components/schemas/ActionResponse'
|
|
407
|
+
resource:
|
|
408
|
+
$ref: '#/components/schemas/ResourceResponse'
|
|
409
|
+
type: object
|
|
410
|
+
required:
|
|
411
|
+
- action
|
|
412
|
+
- resource
|
|
413
|
+
title: ActionResourceResponse
|
|
414
|
+
description: Pairing of an action with a resource.
|
|
415
|
+
ActionResponse:
|
|
416
|
+
properties:
|
|
417
|
+
name:
|
|
418
|
+
type: string
|
|
419
|
+
title: Name
|
|
420
|
+
type: object
|
|
421
|
+
required:
|
|
422
|
+
- name
|
|
423
|
+
title: ActionResponse
|
|
424
|
+
description: Outgoing representation of an action (permission name).
|
|
87
425
|
HTTPExceptionResponse:
|
|
88
426
|
properties:
|
|
89
427
|
detail:
|
|
@@ -106,20 +444,6 @@ components:
|
|
|
106
444
|
title: Detail
|
|
107
445
|
type: object
|
|
108
446
|
title: HTTPValidationError
|
|
109
|
-
LoginBody:
|
|
110
|
-
properties:
|
|
111
|
-
username:
|
|
112
|
-
type: string
|
|
113
|
-
title: Username
|
|
114
|
-
password:
|
|
115
|
-
type: string
|
|
116
|
-
title: Password
|
|
117
|
-
type: object
|
|
118
|
-
required:
|
|
119
|
-
- username
|
|
120
|
-
- password
|
|
121
|
-
title: LoginBody
|
|
122
|
-
description: API Token serializer for requests.
|
|
123
447
|
LoginResponse:
|
|
124
448
|
properties:
|
|
125
449
|
access_token:
|
|
@@ -130,6 +454,64 @@ components:
|
|
|
130
454
|
- access_token
|
|
131
455
|
title: LoginResponse
|
|
132
456
|
description: API Token serializer for responses.
|
|
457
|
+
ResourceResponse:
|
|
458
|
+
properties:
|
|
459
|
+
name:
|
|
460
|
+
type: string
|
|
461
|
+
title: Name
|
|
462
|
+
type: object
|
|
463
|
+
required:
|
|
464
|
+
- name
|
|
465
|
+
title: ResourceResponse
|
|
466
|
+
description: Outgoing representation of a resource.
|
|
467
|
+
RoleBody:
|
|
468
|
+
properties:
|
|
469
|
+
name:
|
|
470
|
+
type: string
|
|
471
|
+
minLength: 1
|
|
472
|
+
title: Name
|
|
473
|
+
actions:
|
|
474
|
+
items:
|
|
475
|
+
$ref: '#/components/schemas/ActionResourceResponse'
|
|
476
|
+
type: array
|
|
477
|
+
title: Actions
|
|
478
|
+
additionalProperties: false
|
|
479
|
+
type: object
|
|
480
|
+
required:
|
|
481
|
+
- name
|
|
482
|
+
title: RoleBody
|
|
483
|
+
description: Incoming payload for creating/updating a role.
|
|
484
|
+
RoleCollectionResponse:
|
|
485
|
+
properties:
|
|
486
|
+
roles:
|
|
487
|
+
items:
|
|
488
|
+
$ref: '#/components/schemas/RoleResponse'
|
|
489
|
+
type: array
|
|
490
|
+
title: Roles
|
|
491
|
+
total_entries:
|
|
492
|
+
type: integer
|
|
493
|
+
title: Total Entries
|
|
494
|
+
type: object
|
|
495
|
+
required:
|
|
496
|
+
- roles
|
|
497
|
+
- total_entries
|
|
498
|
+
title: RoleCollectionResponse
|
|
499
|
+
description: Outgoing representation of a paginated collection of roles.
|
|
500
|
+
RoleResponse:
|
|
501
|
+
properties:
|
|
502
|
+
name:
|
|
503
|
+
type: string
|
|
504
|
+
title: Name
|
|
505
|
+
actions:
|
|
506
|
+
items:
|
|
507
|
+
$ref: '#/components/schemas/ActionResourceResponse'
|
|
508
|
+
type: array
|
|
509
|
+
title: Actions
|
|
510
|
+
type: object
|
|
511
|
+
required:
|
|
512
|
+
- name
|
|
513
|
+
title: RoleResponse
|
|
514
|
+
description: Outgoing representation of a role and its permissions.
|
|
133
515
|
ValidationError:
|
|
134
516
|
properties:
|
|
135
517
|
loc:
|
|
@@ -151,3 +533,21 @@ components:
|
|
|
151
533
|
- msg
|
|
152
534
|
- type
|
|
153
535
|
title: ValidationError
|
|
536
|
+
securitySchemes:
|
|
537
|
+
OAuth2PasswordBearer:
|
|
538
|
+
type: oauth2
|
|
539
|
+
description: To authenticate Airflow API requests, clients must include a JWT
|
|
540
|
+
(JSON Web Token) in the Authorization header of each request. This token is
|
|
541
|
+
used to verify the identity of the client and ensure that they have the appropriate
|
|
542
|
+
permissions to access the requested resources. You can use the endpoint ``POST
|
|
543
|
+
/auth/token`` in order to generate a JWT token. Upon successful authentication,
|
|
544
|
+
the server will issue a JWT token that contains the necessary information
|
|
545
|
+
(such as user identity and scope) to authenticate subsequent requests. To
|
|
546
|
+
learn more about Airflow public API authentication, please read https://airflow.apache.org/docs/apache-airflow/stable/security/api.html.
|
|
547
|
+
flows:
|
|
548
|
+
password:
|
|
549
|
+
scopes: {}
|
|
550
|
+
tokenUrl: /auth/token
|
|
551
|
+
HTTPBearer:
|
|
552
|
+
type: http
|
|
553
|
+
scheme: bearer
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
|
3
|
+
# distributed with this work for additional information
|
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
|
6
|
+
# "License"); you may not use this file except in compliance
|
|
7
|
+
# with the License. You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
|
|
21
|
+
from fastapi import Query
|
|
22
|
+
|
|
23
|
+
from airflow.configuration import conf
|
|
24
|
+
|
|
25
|
+
log = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_effective_limit(default: int = 100):
|
|
29
|
+
"""
|
|
30
|
+
Return a FastAPI dependency that enforces API page limit rules.
|
|
31
|
+
|
|
32
|
+
:param default: Default limit if not provided by client.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def _limit(
|
|
36
|
+
limit: int = Query(
|
|
37
|
+
default,
|
|
38
|
+
ge=0,
|
|
39
|
+
),
|
|
40
|
+
) -> int:
|
|
41
|
+
max_val = conf.getint("api", "maximum_page_limit")
|
|
42
|
+
fallback = conf.getint("api", "fallback_page_limit")
|
|
43
|
+
|
|
44
|
+
if limit == 0:
|
|
45
|
+
return fallback
|
|
46
|
+
if limit > max_val:
|
|
47
|
+
log.warning(
|
|
48
|
+
"The limit param value %s passed in API exceeds the configured maximum page limit %s",
|
|
49
|
+
limit,
|
|
50
|
+
max_val,
|
|
51
|
+
)
|
|
52
|
+
return max_val
|
|
53
|
+
return limit
|
|
54
|
+
|
|
55
|
+
return _limit
|
|
@@ -16,12 +16,19 @@
|
|
|
16
16
|
# under the License.
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from fastapi import Body
|
|
19
22
|
from starlette import status
|
|
23
|
+
from starlette.requests import Request # noqa: TC002
|
|
24
|
+
from starlette.responses import RedirectResponse
|
|
20
25
|
|
|
26
|
+
from airflow.api_fastapi.app import get_auth_manager
|
|
27
|
+
from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN
|
|
21
28
|
from airflow.api_fastapi.common.router import AirflowRouter
|
|
22
29
|
from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc
|
|
23
30
|
from airflow.configuration import conf
|
|
24
|
-
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.login import
|
|
31
|
+
from airflow.providers.fab.auth_manager.api_fastapi.datamodels.login import LoginResponse
|
|
25
32
|
from airflow.providers.fab.auth_manager.api_fastapi.services.login import FABAuthManagerLogin
|
|
26
33
|
from airflow.providers.fab.auth_manager.cli_commands.utils import get_application_builder
|
|
27
34
|
|
|
@@ -34,10 +41,10 @@ login_router = AirflowRouter(tags=["FabAuthManager"])
|
|
|
34
41
|
status_code=status.HTTP_201_CREATED,
|
|
35
42
|
responses=create_openapi_http_exception_doc([status.HTTP_400_BAD_REQUEST, status.HTTP_401_UNAUTHORIZED]),
|
|
36
43
|
)
|
|
37
|
-
def create_token(body:
|
|
44
|
+
def create_token(request: Request, body: dict[str, Any] = Body(...)) -> LoginResponse:
|
|
38
45
|
"""Generate a new API token."""
|
|
39
46
|
with get_application_builder():
|
|
40
|
-
return FABAuthManagerLogin.create_token(body=body)
|
|
47
|
+
return FABAuthManagerLogin.create_token(headers=dict(request.headers), body=body)
|
|
41
48
|
|
|
42
49
|
|
|
43
50
|
@login_router.post(
|
|
@@ -46,9 +53,34 @@ def create_token(body: LoginBody) -> LoginResponse:
|
|
|
46
53
|
status_code=status.HTTP_201_CREATED,
|
|
47
54
|
responses=create_openapi_http_exception_doc([status.HTTP_400_BAD_REQUEST, status.HTTP_401_UNAUTHORIZED]),
|
|
48
55
|
)
|
|
49
|
-
def create_token_cli(body:
|
|
56
|
+
def create_token_cli(request: Request, body: dict[str, Any] = Body(...)) -> LoginResponse:
|
|
50
57
|
"""Generate a new CLI API token."""
|
|
51
58
|
with get_application_builder():
|
|
52
59
|
return FABAuthManagerLogin.create_token(
|
|
53
|
-
|
|
60
|
+
headers=dict(request.headers),
|
|
61
|
+
body=body,
|
|
62
|
+
expiration_time_in_seconds=conf.getint("api_auth", "jwt_cli_expiration_time"),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@login_router.get(
|
|
67
|
+
"/logout",
|
|
68
|
+
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
|
|
69
|
+
)
|
|
70
|
+
def logout(request: Request) -> RedirectResponse:
|
|
71
|
+
"""Generate a new API token."""
|
|
72
|
+
with get_application_builder():
|
|
73
|
+
login_url = get_auth_manager().get_url_login()
|
|
74
|
+
secure = request.base_url.scheme == "https" or bool(conf.get("api", "ssl_cert", fallback=""))
|
|
75
|
+
response = RedirectResponse(login_url)
|
|
76
|
+
response.delete_cookie(
|
|
77
|
+
key="session",
|
|
78
|
+
secure=secure,
|
|
79
|
+
httponly=True,
|
|
80
|
+
)
|
|
81
|
+
response.delete_cookie(
|
|
82
|
+
key=COOKIE_NAME_JWT_TOKEN,
|
|
83
|
+
secure=secure,
|
|
84
|
+
httponly=True,
|
|
54
85
|
)
|
|
86
|
+
return response
|