mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-03-23 18:03:39 +00:00
feat: allow to configure cors
This commit is contained in:
@@ -180,6 +180,10 @@ Web server settings. This feature requires ``aiohttp`` libraries to be installed
|
||||
|
||||
* ``address`` - optional address in form ``proto://host:port`` (``port`` can be omitted in case of default ``proto`` ports), will be used instead of ``http://{host}:{port}`` in case if set, string, optional. This option is required in case if ``OAuth`` provider is used.
|
||||
* ``autorefresh_intervals`` - enable page auto refresh options, space separated list of integers, optional. The first defined interval will be used as default. If no intervals set, the auto refresh buttons will be disabled. If first element of the list equals ``0``, auto refresh will be disabled by default.
|
||||
* ``cors_allow_headers`` - allowed CORS headers, space separated list of strings, optional.
|
||||
* ``cors_allow_methods`` - allowed CORS methods, space separated list of strings, optional.
|
||||
* ``cors_allow_origins`` - allowed CORS origins, space separated list of strings, optional, default ``*``.
|
||||
* ``cors_expose_headers`` - exposed CORS headers, space separated list of strings, optional.
|
||||
* ``enable_archive_upload`` - allow to upload packages via HTTP (i.e. call of ``/api/v1/service/upload`` uri), boolean, optional, default ``no``.
|
||||
* ``host`` - host to bind, string, optional.
|
||||
* ``index_url`` - full URL of the repository index page, string, optional.
|
||||
|
||||
@@ -30,6 +30,14 @@ allow_read_only = yes
|
||||
; If no intervals set, auto refresh will be disabled. 0 can only be the first element and will disable auto refresh
|
||||
; by default.
|
||||
autorefresh_intervals = 5 1 10 30 60
|
||||
; Allowed CORS headers. By default everything is allowed.
|
||||
;cors_allow_headers =
|
||||
; Allowed CORS methods. By default everything is allowed.
|
||||
;cors_allow_methods =
|
||||
; Allowed CORS origins.
|
||||
;cors_allow_origins = *
|
||||
; Exposed CORS headers. By default everything is exposed.
|
||||
;cors_expose_headers =
|
||||
; Enable file upload endpoint used by some triggers.
|
||||
;enable_archive_upload = no
|
||||
; Address to bind the server.
|
||||
|
||||
@@ -358,6 +358,38 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"min": 0,
|
||||
},
|
||||
},
|
||||
"cors_allow_headers": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"cors_allow_methods": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"cors_allow_origins": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"cors_expose_headers": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"enable_archive_upload": {
|
||||
"type": "boolean",
|
||||
"coerce": "boolean",
|
||||
|
||||
@@ -21,26 +21,34 @@ import aiohttp_cors
|
||||
|
||||
from aiohttp.web import Application
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
__all__ = ["setup_cors"]
|
||||
|
||||
|
||||
def setup_cors(application: Application) -> aiohttp_cors.CorsConfig:
|
||||
def setup_cors(application: Application, configuration: Configuration) -> aiohttp_cors.CorsConfig:
|
||||
"""
|
||||
setup CORS for the web application
|
||||
|
||||
Args:
|
||||
application(Application): web application instance
|
||||
configuration(Configuration): configuration instance
|
||||
|
||||
Returns:
|
||||
aiohttp_cors.CorsConfig: generated CORS configuration
|
||||
"""
|
||||
allow_headers = configuration.getlist("web", "cors_allow_headers", fallback=[]) or "*"
|
||||
allow_methods = configuration.getlist("web", "cors_allow_methods", fallback=[]) or "*"
|
||||
expose_headers = configuration.getlist("web", "cors_expose_headers", fallback=[]) or "*"
|
||||
|
||||
cors = aiohttp_cors.setup(application, defaults={
|
||||
"*": aiohttp_cors.ResourceOptions( # type: ignore[no-untyped-call]
|
||||
expose_headers="*",
|
||||
allow_headers="*",
|
||||
allow_methods="*",
|
||||
origin: aiohttp_cors.ResourceOptions( # type: ignore[no-untyped-call]
|
||||
expose_headers=expose_headers,
|
||||
allow_headers=allow_headers,
|
||||
allow_methods=allow_methods,
|
||||
)
|
||||
for origin in configuration.getlist("web", "cors_allow_origins", fallback=["*"])
|
||||
})
|
||||
for route in application.router.routes():
|
||||
cors.add(route)
|
||||
|
||||
@@ -187,7 +187,7 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis
|
||||
setup_routes(application, configuration)
|
||||
|
||||
application.logger.info("setup CORS")
|
||||
setup_cors(application)
|
||||
setup_cors(application, configuration)
|
||||
|
||||
application.logger.info("setup templates")
|
||||
loader = jinja2.FileSystemLoader(searchpath=configuration.getpathlist("web", "templates"))
|
||||
|
||||
@@ -2,13 +2,17 @@ import aiohttp_cors
|
||||
import pytest
|
||||
|
||||
from aiohttp.web import Application
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.web.cors import setup_cors
|
||||
from ahriman.web.keys import ConfigurationKey
|
||||
|
||||
|
||||
def test_setup_cors(application: Application) -> None:
|
||||
"""
|
||||
must setup CORS
|
||||
"""
|
||||
cors: aiohttp_cors.CorsConfig = application[aiohttp_cors.APP_CONFIG_KEY]
|
||||
cors = application[aiohttp_cors.APP_CONFIG_KEY]
|
||||
# let's test here that it is enabled for all requests
|
||||
for route in application.router.routes():
|
||||
# we don't want to deal with match info here though
|
||||
@@ -18,3 +22,34 @@ def test_setup_cors(application: Application) -> None:
|
||||
continue
|
||||
request = pytest.helpers.request(application, url, route.method, resource=route.resource)
|
||||
assert cors._cors_impl._router_adapter.is_cors_enabled_on_request(request)
|
||||
|
||||
|
||||
def test_setup_cors_custom_origins(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must setup CORS with custom origins
|
||||
"""
|
||||
configuration = application[ConfigurationKey]
|
||||
configuration.set_option("web", "cors_allow_origins", "https://example.com https://httpbin.com")
|
||||
|
||||
setup_mock = mocker.patch("ahriman.web.cors.aiohttp_cors.setup", return_value=mocker.MagicMock())
|
||||
setup_cors(application, configuration)
|
||||
|
||||
defaults = setup_mock.call_args.kwargs["defaults"]
|
||||
assert "https://example.com" in defaults
|
||||
assert "https://httpbin.com" in defaults
|
||||
assert "*" not in defaults
|
||||
|
||||
|
||||
def test_setup_cors_custom_methods(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must setup CORS with custom methods
|
||||
"""
|
||||
configuration = application[ConfigurationKey]
|
||||
configuration.set_option("web", "cors_allow_methods", "GET POST")
|
||||
|
||||
setup_mock = mocker.patch("ahriman.web.cors.aiohttp_cors.setup", return_value=mocker.MagicMock())
|
||||
setup_cors(application, configuration)
|
||||
|
||||
defaults = setup_mock.call_args.kwargs["defaults"]
|
||||
resource_options = next(iter(defaults.values()))
|
||||
assert resource_options.allow_methods == {"GET", "POST"}
|
||||
|
||||
Reference in New Issue
Block a user