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.
Files changed (51) hide show
  1. airflow/providers/fab/__init__.py +1 -1
  2. airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +2 -2
  3. airflow/providers/fab/auth_manager/api_fastapi/datamodels/login.py +0 -7
  4. airflow/providers/fab/auth_manager/api_fastapi/datamodels/roles.py +63 -0
  5. airflow/providers/fab/auth_manager/api_fastapi/openapi/v2-fab-auth-manager-generated.yaml +416 -16
  6. airflow/providers/fab/auth_manager/api_fastapi/parameters.py +55 -0
  7. airflow/providers/fab/auth_manager/api_fastapi/routes/login.py +37 -5
  8. airflow/providers/fab/auth_manager/api_fastapi/routes/roles.py +137 -0
  9. airflow/providers/fab/auth_manager/api_fastapi/security.py +32 -0
  10. airflow/providers/fab/auth_manager/api_fastapi/services/login.py +12 -25
  11. airflow/providers/fab/auth_manager/api_fastapi/services/roles.py +158 -0
  12. airflow/providers/fab/auth_manager/api_fastapi/sorting.py +49 -0
  13. airflow/providers/fab/auth_manager/cli_commands/permissions_command.py +6 -2
  14. airflow/providers/fab/auth_manager/fab_auth_manager.py +33 -3
  15. airflow/providers/fab/auth_manager/models/__init__.py +3 -8
  16. airflow/providers/fab/auth_manager/models/db.py +1 -1
  17. airflow/providers/fab/auth_manager/security_manager/override.py +60 -17
  18. airflow/providers/fab/version_compat.py +1 -0
  19. airflow/providers/fab/www/api_connexion/parameters.py +1 -46
  20. airflow/providers/fab/www/app.py +13 -10
  21. airflow/providers/fab/www/extensions/init_appbuilder.py +5 -2
  22. airflow/providers/fab/www/extensions/init_security.py +1 -1
  23. airflow/providers/fab/www/extensions/init_views.py +11 -7
  24. airflow/providers/fab/www/package-lock.json +417 -265
  25. airflow/providers/fab/www/package.json +13 -10
  26. airflow/providers/fab/www/session.py +5 -8
  27. airflow/providers/fab/www/static/dist/{743.935ed3d26e56ed8f63d3.js → 743.0c0bf201ae17e66a9a3f.js} +1 -1
  28. airflow/providers/fab/www/static/dist/{main.3cf3be1a0c5439bb640d.js → main.bc1f701c3d133e2a3bab.js} +1 -1
  29. airflow/providers/fab/www/static/dist/manifest.json +13 -13
  30. airflow/providers/fab/www/views.py +18 -14
  31. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/METADATA +15 -14
  32. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/RECORD +51 -45
  33. /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
  34. /airflow/providers/fab/www/static/dist/{743.935ed3d26e56ed8f63d3.js.LICENSE.txt → 743.0c0bf201ae17e66a9a3f.js.LICENSE.txt} +0 -0
  35. /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.ff5a35f322070b094aa2.css → airflowDefaultTheme.ef6fc04c9b6920cd75c9.css} +0 -0
  36. /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.ff5a35f322070b094aa2.js → airflowDefaultTheme.ef6fc04c9b6920cd75c9.js} +0 -0
  37. /airflow/providers/fab/www/static/dist/{flash.5583a9e0cf11f2be93da.css → flash.eaaf777ec1b3628cf7be.css} +0 -0
  38. /airflow/providers/fab/www/static/dist/{flash.5583a9e0cf11f2be93da.js → flash.eaaf777ec1b3628cf7be.js} +0 -0
  39. /airflow/providers/fab/www/static/dist/{loadingDots.2e5f555f0753107b0300.css → loadingDots.76f4332c0a932c3dc08f.css} +0 -0
  40. /airflow/providers/fab/www/static/dist/{loadingDots.2e5f555f0753107b0300.js → loadingDots.76f4332c0a932c3dc08f.js} +0 -0
  41. /airflow/providers/fab/www/static/dist/{main.3cf3be1a0c5439bb640d.css → main.bc1f701c3d133e2a3bab.css} +0 -0
  42. /airflow/providers/fab/www/static/dist/{main.3cf3be1a0c5439bb640d.js.LICENSE.txt → main.bc1f701c3d133e2a3bab.js.LICENSE.txt} +0 -0
  43. /airflow/providers/fab/www/static/dist/{materialIcons.3e67dd6fbfcc4f3b5105.css → materialIcons.ad07a489b2f0fc1a96bf.css} +0 -0
  44. /airflow/providers/fab/www/static/dist/{materialIcons.3e67dd6fbfcc4f3b5105.js → materialIcons.ad07a489b2f0fc1a96bf.js} +0 -0
  45. /airflow/providers/fab/www/static/dist/{moment.9baee5ec3d7639a10897.js → moment.5b85b4f6be2fe9c405ac.js} +0 -0
  46. /airflow/providers/fab/www/static/dist/{runtime.6ad9da077ea169d60db9.js → runtime.254c277d91ce3ac79c64.js} +0 -0
  47. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/WHEEL +0 -0
  48. {apache_airflow_providers_fab-3.0.1.dist-info → apache_airflow_providers_fab-3.1.1rc1.dist-info}/entry_points.txt +0 -0
  49. {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
  50. {airflow/providers/fab → apache_airflow_providers_fab-3.1.1rc1.dist-info/licenses}/LICENSE +0 -0
  51. {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.0.1"
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 field in update_mask:
166
- field = field.strip()
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:
@@ -23,10 +23,3 @@ class LoginResponse(BaseModel):
23
23
  """API Token serializer for responses."""
24
24
 
25
25
  access_token: str
26
-
27
-
28
- class LoginBody(BaseModel):
29
- """API Token serializer for requests."""
30
-
31
- username: str
32
- password: str
@@ -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
- $ref: '#/components/schemas/LoginBody'
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
- $ref: '#/components/schemas/LoginBody'
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 LoginBody, LoginResponse
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: LoginBody) -> LoginResponse:
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: LoginBody) -> LoginResponse:
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
- body=body, expiration_time_in_seconds=conf.getint("api_auth", "jwt_cli_expiration_time")
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