mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-10-30 21:33:43 +00:00 
			
		
		
		
	add ability to read cookie secret from config
This commit is contained in:
		| @ -50,6 +50,7 @@ Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` libr | ||||
| * ``allow_read_only`` - allow requesting status APIs without authorization, boolean, required. | ||||
| * ``client_id`` - OAuth2 application client ID, string, required in case if ``oauth`` is used. | ||||
| * ``client_secret`` - OAuth2 application client secret key, string, required in case if ``oauth`` is used. | ||||
| * ``cookie_secret_key`` - secret key which will be used for cookies encryption, string, optional. It must be 32 url-safe base64-encoded bytes and can be generated as following ``base64.urlsafe_b64encode(os.urandom(32)).decode("utf8")``. If not set, it will be generated automatically; note, however, that in this case, all sessions will be automatically expired during restart. | ||||
| * ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days. | ||||
| * ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used. | ||||
| * ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used. | ||||
|  | ||||
| @ -109,6 +109,9 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = { | ||||
|             "client_secret": { | ||||
|                 "type": "string", | ||||
|             }, | ||||
|             "cookie_secret_key": { | ||||
|                 "type": "string", | ||||
|             }, | ||||
|             "max_age": { | ||||
|                 "type": "integer", | ||||
|                 "coerce": "integer", | ||||
|  | ||||
| @ -18,7 +18,6 @@ | ||||
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| import aiohttp_security  # type: ignore | ||||
| import base64 | ||||
| import socket | ||||
| import types | ||||
|  | ||||
| @ -32,12 +31,13 @@ from cryptography import fernet | ||||
| from typing import Optional | ||||
|  | ||||
| from ahriman.core.auth import Auth | ||||
| from ahriman.core.configuration import Configuration | ||||
| from ahriman.models.user_access import UserAccess | ||||
| from ahriman.models.user_identity import UserIdentity | ||||
| from ahriman.web.middlewares import HandlerType, MiddlewareType | ||||
|  | ||||
|  | ||||
| __all__ = ["AuthorizationPolicy", "auth_handler", "setup_auth"] | ||||
| __all__ = ["AuthorizationPolicy", "auth_handler", "cookie_secret_key", "setup_auth"] | ||||
|  | ||||
|  | ||||
| class AuthorizationPolicy(aiohttp_security.AbstractAuthorizationPolicy):  # type: ignore | ||||
| @ -125,19 +125,36 @@ def auth_handler(allow_read_only: bool) -> MiddlewareType: | ||||
|     return handle | ||||
|  | ||||
|  | ||||
| def setup_auth(application: web.Application, validator: Auth) -> web.Application: | ||||
| def cookie_secret_key(configuration: Configuration) -> fernet.Fernet: | ||||
|     """ | ||||
|     extract cookie secret key from configuration if set or generate new one | ||||
|  | ||||
|     Args: | ||||
|         configuration(Configuration): configuration instance | ||||
|  | ||||
|     Returns: | ||||
|         fernet.Fernet: fernet key instance | ||||
|     """ | ||||
|     if (secret_key := configuration.get("auth", "cookie_secret_key", fallback=None)) is not None: | ||||
|         return fernet.Fernet(secret_key) | ||||
|  | ||||
|     secret_key = fernet.Fernet.generate_key() | ||||
|     return fernet.Fernet(secret_key) | ||||
|  | ||||
|  | ||||
| def setup_auth(application: web.Application, configuration: Configuration, validator: Auth) -> web.Application: | ||||
|     """ | ||||
|     setup authorization policies for the application | ||||
|  | ||||
|     Args: | ||||
|         application(web.Application): web application instance | ||||
|         configuration(Configuration): configuration instance | ||||
|         validator(Auth): authorization module instance | ||||
|  | ||||
|     Returns: | ||||
|         web.Application: configured web application | ||||
|     """ | ||||
|     fernet_key = fernet.Fernet.generate_key() | ||||
|     secret_key = base64.urlsafe_b64decode(fernet_key) | ||||
|     secret_key = cookie_secret_key(configuration) | ||||
|     storage = EncryptedCookieStorage(secret_key, cookie_name="API_SESSION", max_age=validator.max_age) | ||||
|     setup_session(application, storage) | ||||
|  | ||||
|  | ||||
| @ -168,6 +168,6 @@ def setup_service(architecture: str, configuration: Configuration, spawner: Spaw | ||||
|     validator = application["validator"] = Auth.load(configuration, database) | ||||
|     if validator.enabled: | ||||
|         from ahriman.web.middlewares.auth_handler import setup_auth | ||||
|         setup_auth(application, validator) | ||||
|         setup_auth(application, configuration, validator) | ||||
|  | ||||
|     return application | ||||
|  | ||||
| @ -3,14 +3,16 @@ import socket | ||||
|  | ||||
| from aiohttp import web | ||||
| from aiohttp.test_utils import TestClient | ||||
| from cryptography import fernet | ||||
| from pytest_mock import MockerFixture | ||||
| from unittest.mock import AsyncMock | ||||
|  | ||||
| from ahriman.core.auth import Auth | ||||
| from ahriman.core.configuration import Configuration | ||||
| from ahriman.models.user import User | ||||
| from ahriman.models.user_access import UserAccess | ||||
| from ahriman.models.user_identity import UserIdentity | ||||
| from ahriman.web.middlewares.auth_handler import auth_handler, AuthorizationPolicy, setup_auth | ||||
| from ahriman.web.middlewares.auth_handler import AuthorizationPolicy, auth_handler, cookie_secret_key, setup_auth | ||||
|  | ||||
|  | ||||
| def _identity(username: str) -> str: | ||||
| @ -175,11 +177,28 @@ async def test_auth_handler_write(mocker: MockerFixture) -> None: | ||||
|         check_permission_mock.assert_called_once_with(aiohttp_request, UserAccess.Full, aiohttp_request.path) | ||||
|  | ||||
|  | ||||
| def test_setup_auth(application_with_auth: web.Application, auth: Auth, mocker: MockerFixture) -> None: | ||||
| def test_cookie_secret_key(configuration: Configuration) -> None: | ||||
|     """ | ||||
|     must generate fernet key | ||||
|     """ | ||||
|     secret_key = cookie_secret_key(configuration) | ||||
|     assert isinstance(secret_key, fernet.Fernet) | ||||
|  | ||||
|  | ||||
| def test_cookie_secret_key_cached(configuration: Configuration) -> None: | ||||
|     """ | ||||
|     must use cookie key as set by configuration | ||||
|     """ | ||||
|     configuration.set_option("auth", "cookie_secret_key", fernet.Fernet.generate_key().decode("utf8")) | ||||
|     assert cookie_secret_key(configuration) is not None | ||||
|  | ||||
|  | ||||
| def test_setup_auth(application_with_auth: web.Application, configuration: Configuration, auth: Auth, | ||||
|                     mocker: MockerFixture) -> None: | ||||
|     """ | ||||
|     must set up authorization | ||||
|     """ | ||||
|     setup_mock = mocker.patch("aiohttp_security.setup") | ||||
|     application = setup_auth(application_with_auth, auth) | ||||
|     application = setup_auth(application_with_auth, configuration, auth) | ||||
|     assert application.get("validator") is not None | ||||
|     setup_mock.assert_called_once_with(application_with_auth, pytest.helpers.anyvar(int), pytest.helpers.anyvar(int)) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user