Repository: apache/airflow
Pull request: Enable multiple cors url & doc
Close: https://github.com/apache/airflow/issues/17869
<!--
Thank you for contributing! Please make sure that your code changes
are covered with tests. And in case of new features or big changes
remember to adjust the documentation.

Feel free to ping committers for the review!

In case of existing issue, reference it using one of the following:

closes: #ISSUE
related: #ISSUE

How to write a good git commit message:
http://chris.beams.io/posts/git-commit/
-->

---
**^ Add meaningful description above**

Read the **[Pull Request Guidelines](https://github.com/apache/airflow/blob/main/CONTRIBUTING.rst#pull-request-guidelines)** for more information.
In case of fundamental code change, Airflow Improvement Proposal ([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvements+Proposals)) is needed.
In case of a new dependency, check compliance with the [ASF 3rd Party License Policy](https://www.apache.org/legal/resolved.html#category-x).
In case of backwards incompatible changes please leave a note in [UPDATING.md](https://github.com/apache/airflow/blob/main/UPDATING.md).


Problem statement:
More than one url in cors header
**Description**

Enabled list of cors headers origin allowed

**Use case / motivation**

I'm currently developping Django application working with apache airflow for some external work through api. From Django, user run dag and a js request wait for dag end. By this way, I enabled cors headers in airflow.cfg with correct url. But now, I've  environment (dev & prod). It could be useful to be able to set more than one url in airflow.cfg cors header origin allowed

Thank's in advance :)

Hints from issue discussion:
Thanks for opening your first issue here! Be sure to follow the issue template!

Would you be willing to work on this and make a PR? 
Yes sure, I could work on it within next week
Hi, I switched to my pro account. I've just create a PR: https://github.com/apache/airflow/pull/17941. Feel free if you have some comments

Commit message: Adding backward

Patch to review:
diff --git a/airflow/config_templates/config.yml b/airflow/config_templates/config.yml
index 4e5241dd0b823..8a135fef8bf11 100644
--- a/airflow/config_templates/config.yml
+++ b/airflow/config_templates/config.yml
@@ -817,9 +817,9 @@
       version_added: ~
       example: ~
       default: ""
-    - name: access_control_allow_origin
+    - name: access_control_allow_origins
       description: |
-        Indicates whether the response can be shared with requesting code from the given origin.
+        Indicates whether the response can be shared with requesting code from the given origins. Separate urls with space.
       type: string
       version_added: ~
       example: ~
diff --git a/airflow/config_templates/default_airflow.cfg b/airflow/config_templates/default_airflow.cfg
index f3c6bbf6067b6..d1d7c2b16e549 100644
--- a/airflow/config_templates/default_airflow.cfg
+++ b/airflow/config_templates/default_airflow.cfg
@@ -445,8 +445,8 @@ access_control_allow_headers =
 # Specifies the method or methods allowed when accessing the resource.
 access_control_allow_methods =
 
-# Indicates whether the response can be shared with requesting code from the given origin.
-access_control_allow_origin =
+# Indicates whether the response can be shared with requesting code from the given origins.
+access_control_allow_origins =
 
 [lineage]
 # what lineage backend to use
diff --git a/airflow/configuration.py b/airflow/configuration.py
index 88975459d702a..64e09549aeee4 100644
--- a/airflow/configuration.py
+++ b/airflow/configuration.py
@@ -172,6 +172,7 @@ class AirflowConfigParser(ConfigParser):
         ('core', 'default_pool_task_slot_count'): ('core', 'non_pooled_task_slot_count', '1.10.4'),
         ('core', 'max_active_tasks_per_dag'): ('core', 'dag_concurrency', '2.2.0'),
         ('logging', 'worker_log_server_port'): ('celery', 'worker_log_server_port', '2.3.0'),
+        ('api', 'access_control_allow_origins'): ('api', 'access_control_allow_origin', '2.1.3'),
     }
 
     # A mapping of old default values that we want to change and warn the user
diff --git a/airflow/www/extensions/init_views.py b/airflow/www/extensions/init_views.py
index 664757c2eb69c..24ce2779315f5 100644
--- a/airflow/www/extensions/init_views.py
+++ b/airflow/www/extensions/init_views.py
@@ -154,13 +154,16 @@ def set_cors_headers_on_response(response):
     """Add response headers"""
     allow_headers = conf.get('api', 'access_control_allow_headers')
     allow_methods = conf.get('api', 'access_control_allow_methods')
-    allow_origin = conf.get('api', 'access_control_allow_origin')
+    allow_origins = conf.get('api', 'access_control_allow_origins')
     if allow_headers is not None:
         response.headers['Access-Control-Allow-Headers'] = allow_headers
     if allow_methods is not None:
         response.headers['Access-Control-Allow-Methods'] = allow_methods
-    if allow_origin is not None:
-        response.headers['Access-Control-Allow-Origin'] = allow_origin
+    if allow_origins is not None:
+        allowed_origins = allow_origins.split(' ')
+        origin = request.environ.get('HTTP_ORIGIN', allowed_origins[0])
+        if origin in allowed_origins:
+            response.headers['Access-Control-Allow-Origin'] = origin
     return response
 
 
diff --git a/docs/apache-airflow/security/api.rst b/docs/apache-airflow/security/api.rst
index 70c2b8ed83d2d..872553e813331 100644
--- a/docs/apache-airflow/security/api.rst
+++ b/docs/apache-airflow/security/api.rst
@@ -145,7 +145,7 @@ from scripts running in the browser.
 ``Access-Control-Allow-Headers``, ``Access-Control-Allow-Methods``, and
 ``Access-Control-Allow-Origin`` headers can be added by setting values for
 ``access_control_allow_headers``, ``access_control_allow_methods``, and
-``access_control_allow_origin`` options in the ``[api]`` section of the
+``access_control_allow_origins`` options in the ``[api]`` section of the
 ``airflow.cfg`` file.
 
 .. code-block:: ini
@@ -153,7 +153,7 @@ from scripts running in the browser.
     [api]
     access_control_allow_headers = origin, content-type, accept
     access_control_allow_methods = POST, GET, OPTIONS, DELETE
-    access_control_allow_origin = https://exampleclientapp.com
+    access_control_allow_origins = https://exampleclientapp1.com https://exampleclientapp2.com
 
 Page size limit
 ---------------


Code review comments:
diff_hunk: @@ -172,6 +172,7 @@ class AirflowConfigParser(ConfigParser):
         ('core', 'default_pool_task_slot_count'): ('core', 'non_pooled_task_slot_count', '1.10.4'),
         ('core', 'max_active_tasks_per_dag'): ('core', 'dag_concurrency', '2.2.0'),
         ('logging', 'worker_log_server_port'): ('celery', 'worker_log_server_port', '2.3.0'),
+        ('api', 'access_control_allow_origins'): ('api', 'access_control_allow_origin', '2.1.3'),
path: airflow/configuration.py
text: @user1:
```suggestion
        ('api', 'access_control_allow_origins'): ('api', 'access_control_allow_origin', '2.2.0'),
```