Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa91063211 | ||
|
|
fa2d5d40dd | ||
|
|
6ccef162f6 | ||
|
|
48dac139a5 |
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-core"
|
name = "govoplan-core"
|
||||||
version = "0.1.30"
|
version = "0.1.34"
|
||||||
description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components."
|
description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
from collections.abc import Iterable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Mapping
|
from typing import Mapping
|
||||||
|
|
||||||
@@ -12,6 +13,12 @@ from govoplan_core.security.outbound_http import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
MAX_OUTBOUND_HTTP_REQUEST_BODY_BYTES = 1_000_000
|
||||||
|
_STANDARD_REDIRECT_SENSITIVE_HEADERS = frozenset(
|
||||||
|
{"authorization", "proxy-authorization", "cookie", "cookie2"}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class HttpFetchResponse:
|
class HttpFetchResponse:
|
||||||
status: int
|
status: int
|
||||||
@@ -46,15 +53,27 @@ def fetch_http(
|
|||||||
label: str = "URL",
|
label: str = "URL",
|
||||||
method: str = "GET",
|
method: str = "GET",
|
||||||
headers: Mapping[str, str] | None = None,
|
headers: Mapping[str, str] | None = None,
|
||||||
|
body: bytes | None = None,
|
||||||
max_bytes: int | None = None,
|
max_bytes: int | None = None,
|
||||||
|
redirect_sensitive_headers: Iterable[str] = (),
|
||||||
) -> HttpFetchResponse:
|
) -> HttpFetchResponse:
|
||||||
|
if body is not None and len(body) > MAX_OUTBOUND_HTTP_REQUEST_BODY_BYTES:
|
||||||
|
raise ValueError(
|
||||||
|
"Outbound HTTP request body exceeds the 1000000-byte safety limit."
|
||||||
|
)
|
||||||
validated_url = validate_outbound_http_url(url, label=label)
|
validated_url = validate_outbound_http_url(url, label=label)
|
||||||
request = urllib.request.Request( # noqa: S310 - URL is restricted to validated HTTP(S).
|
request = urllib.request.Request( # noqa: S310 - URL is restricted to validated HTTP(S).
|
||||||
validated_url,
|
validated_url,
|
||||||
|
data=body,
|
||||||
headers=dict(headers or {}),
|
headers=dict(headers or {}),
|
||||||
method=method,
|
method=method,
|
||||||
)
|
)
|
||||||
opener = build_outbound_http_opener(_PolicyRedirectHandler(label=label))
|
opener = build_outbound_http_opener(
|
||||||
|
_PolicyRedirectHandler(
|
||||||
|
label=label,
|
||||||
|
sensitive_headers=redirect_sensitive_headers,
|
||||||
|
)
|
||||||
|
)
|
||||||
with opener.open(request, timeout=timeout) as response: # noqa: S310 - URL and every redirect are policy-validated. # nosec B310 # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
|
with opener.open(request, timeout=timeout) as response: # noqa: S310 - URL and every redirect are policy-validated. # nosec B310 # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
|
||||||
response_headers = dict(response.headers.items())
|
response_headers = dict(response.headers.items())
|
||||||
return HttpFetchResponse(
|
return HttpFetchResponse(
|
||||||
@@ -76,16 +95,35 @@ def fetch_http_text(
|
|||||||
label: str = "URL",
|
label: str = "URL",
|
||||||
method: str = "GET",
|
method: str = "GET",
|
||||||
headers: Mapping[str, str] | None = None,
|
headers: Mapping[str, str] | None = None,
|
||||||
|
body: bytes | None = None,
|
||||||
encoding: str = "utf-8",
|
encoding: str = "utf-8",
|
||||||
max_bytes: int | None = None,
|
max_bytes: int | None = None,
|
||||||
|
redirect_sensitive_headers: Iterable[str] = (),
|
||||||
) -> str:
|
) -> str:
|
||||||
return fetch_http(url, timeout=timeout, label=label, method=method, headers=headers, max_bytes=max_bytes).text(encoding)
|
return fetch_http(
|
||||||
|
url,
|
||||||
|
timeout=timeout,
|
||||||
|
label=label,
|
||||||
|
method=method,
|
||||||
|
headers=headers,
|
||||||
|
body=body,
|
||||||
|
max_bytes=max_bytes,
|
||||||
|
redirect_sensitive_headers=redirect_sensitive_headers,
|
||||||
|
).text(encoding)
|
||||||
|
|
||||||
|
|
||||||
class _PolicyRedirectHandler(urllib.request.HTTPRedirectHandler):
|
class _PolicyRedirectHandler(urllib.request.HTTPRedirectHandler):
|
||||||
def __init__(self, *, label: str) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
label: str,
|
||||||
|
sensitive_headers: Iterable[str] = (),
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._label = label
|
self._label = label
|
||||||
|
self._sensitive_headers = _STANDARD_REDIRECT_SENSITIVE_HEADERS | {
|
||||||
|
value.strip().lower() for value in sensitive_headers if value.strip()
|
||||||
|
}
|
||||||
|
|
||||||
def redirect_request(self, req, fp, code, msg, headers, newurl): # type: ignore[no-untyped-def]
|
def redirect_request(self, req, fp, code, msg, headers, newurl): # type: ignore[no-untyped-def]
|
||||||
candidate = validate_outbound_http_url(newurl, label=f"{self._label} redirect")
|
candidate = validate_outbound_http_url(newurl, label=f"{self._label} redirect")
|
||||||
@@ -95,8 +133,9 @@ class _PolicyRedirectHandler(urllib.request.HTTPRedirectHandler):
|
|||||||
return None
|
return None
|
||||||
new_request = super().redirect_request(req, fp, code, msg, headers, candidate)
|
new_request = super().redirect_request(req, fp, code, msg, headers, candidate)
|
||||||
if new_request is not None and _http_origin(previous) != _http_origin(redirected):
|
if new_request is not None and _http_origin(previous) != _http_origin(redirected):
|
||||||
for header in ("Authorization", "Proxy-Authorization", "Cookie", "Cookie2"):
|
for header in tuple(new_request.headers) + tuple(new_request.unredirected_hdrs):
|
||||||
new_request.remove_header(header)
|
if header.lower() in self._sensitive_headers:
|
||||||
|
new_request.remove_header(header)
|
||||||
return new_request
|
return new_request
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from govoplan_core.security.http_fetch import _PolicyRedirectHandler, is_http_url, validate_http_url
|
from govoplan_core.security.http_fetch import (
|
||||||
|
_PolicyRedirectHandler,
|
||||||
|
fetch_http,
|
||||||
|
is_http_url,
|
||||||
|
validate_http_url,
|
||||||
|
)
|
||||||
from govoplan_core.security.outbound_http import (
|
from govoplan_core.security.outbound_http import (
|
||||||
DEFAULT_FILE_TRANSFER_BYTES,
|
DEFAULT_FILE_TRANSFER_BYTES,
|
||||||
DEFAULT_STRUCTURED_RESPONSE_BYTES,
|
DEFAULT_STRUCTURED_RESPONSE_BYTES,
|
||||||
@@ -21,6 +26,51 @@ from govoplan_core.security.outbound_http import (
|
|||||||
|
|
||||||
|
|
||||||
class HttpFetchTests(unittest.TestCase):
|
class HttpFetchTests(unittest.TestCase):
|
||||||
|
def test_fetch_http_forwards_a_bounded_request_body(self) -> None:
|
||||||
|
class Response(io.BytesIO):
|
||||||
|
status = 200
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *_args):
|
||||||
|
return False
|
||||||
|
|
||||||
|
opener = Mock()
|
||||||
|
opener.open.return_value = Response(b"{}")
|
||||||
|
with patch(
|
||||||
|
"govoplan_core.security.http_fetch.validate_outbound_http_url",
|
||||||
|
return_value="https://wiki.example.test/api.php",
|
||||||
|
), patch(
|
||||||
|
"govoplan_core.security.http_fetch.build_outbound_http_opener",
|
||||||
|
return_value=opener,
|
||||||
|
):
|
||||||
|
response = fetch_http(
|
||||||
|
"https://wiki.example.test/api.php",
|
||||||
|
method="POST",
|
||||||
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||||
|
body=b"action=edit",
|
||||||
|
max_bytes=1024,
|
||||||
|
)
|
||||||
|
|
||||||
|
request = opener.open.call_args.args[0]
|
||||||
|
self.assertEqual("POST", request.get_method())
|
||||||
|
self.assertEqual(b"action=edit", request.data)
|
||||||
|
self.assertEqual(b"{}", response.body)
|
||||||
|
|
||||||
|
def test_fetch_http_rejects_an_oversized_request_body_before_transport(self) -> None:
|
||||||
|
with patch(
|
||||||
|
"govoplan_core.security.http_fetch.validate_outbound_http_url"
|
||||||
|
) as validate:
|
||||||
|
with self.assertRaisesRegex(ValueError, "request body exceeds"):
|
||||||
|
fetch_http(
|
||||||
|
"https://wiki.example.test/api.php",
|
||||||
|
method="POST",
|
||||||
|
body=b"x" * 1_000_001,
|
||||||
|
)
|
||||||
|
validate.assert_not_called()
|
||||||
|
|
||||||
def test_validate_http_url_accepts_absolute_http_urls_without_credentials(self) -> None:
|
def test_validate_http_url_accepts_absolute_http_urls_without_credentials(self) -> None:
|
||||||
self.assertEqual("https://example.test/catalog.json", validate_http_url("https://example.test/catalog.json"))
|
self.assertEqual("https://example.test/catalog.json", validate_http_url("https://example.test/catalog.json"))
|
||||||
self.assertTrue(is_http_url("http://example.test/catalog.json"))
|
self.assertTrue(is_http_url("http://example.test/catalog.json"))
|
||||||
@@ -189,9 +239,17 @@ class HttpFetchTests(unittest.TestCase):
|
|||||||
|
|
||||||
request = urllib.request.Request(
|
request = urllib.request.Request(
|
||||||
"https://catalog.example.test/releases",
|
"https://catalog.example.test/releases",
|
||||||
headers={"Authorization": "Bearer secret", "X-Request-ID": "request-1"},
|
headers={
|
||||||
|
"Authorization": "Bearer secret",
|
||||||
|
"Cookie": "session=secret",
|
||||||
|
"X-OTRS-Header-Password": "secret",
|
||||||
|
"X-Request-ID": "request-1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
handler = _PolicyRedirectHandler(
|
||||||
|
label="Catalog URL",
|
||||||
|
sensitive_headers=("X-OTRS-Header-Password",),
|
||||||
)
|
)
|
||||||
handler = _PolicyRedirectHandler(label="Catalog URL")
|
|
||||||
with patch.dict("os.environ", {"APP_ENV": "test"}), patch(
|
with patch.dict("os.environ", {"APP_ENV": "test"}), patch(
|
||||||
"govoplan_core.security.outbound_http.socket.getaddrinfo",
|
"govoplan_core.security.outbound_http.socket.getaddrinfo",
|
||||||
return_value=[(2, 1, 6, "", ("127.0.0.1", 443))],
|
return_value=[(2, 1, 6, "", ("127.0.0.1", 443))],
|
||||||
@@ -215,9 +273,38 @@ class HttpFetchTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertIsNotNone(redirected)
|
self.assertIsNotNone(redirected)
|
||||||
self.assertIsNone(redirected.get_header("Authorization"))
|
self.assertIsNone(redirected.get_header("Authorization"))
|
||||||
|
self.assertIsNone(redirected.get_header("Cookie"))
|
||||||
|
self.assertIsNone(redirected.get_header("X-otrs-header-password"))
|
||||||
self.assertEqual("request-1", redirected.get_header("X-request-id"))
|
self.assertEqual("request-1", redirected.get_header("X-request-id"))
|
||||||
self.assertIsNone(downgrade)
|
self.assertIsNone(downgrade)
|
||||||
|
|
||||||
|
def test_core_redirects_preserve_caller_sensitive_headers_on_the_same_origin(self) -> None:
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
request = urllib.request.Request(
|
||||||
|
"https://desk.example.test/original",
|
||||||
|
headers={"X-OTRS-Header-SessionID": "secret"},
|
||||||
|
)
|
||||||
|
handler = _PolicyRedirectHandler(
|
||||||
|
label="Service-desk URL",
|
||||||
|
sensitive_headers=("X-OTRS-Header-SessionID",),
|
||||||
|
)
|
||||||
|
with patch.dict("os.environ", {"APP_ENV": "test"}), patch(
|
||||||
|
"govoplan_core.security.outbound_http.socket.getaddrinfo",
|
||||||
|
return_value=[(2, 1, 6, "", ("127.0.0.1", 443))],
|
||||||
|
):
|
||||||
|
redirected = handler.redirect_request(
|
||||||
|
request,
|
||||||
|
None,
|
||||||
|
302,
|
||||||
|
"Found",
|
||||||
|
{},
|
||||||
|
"https://desk.example.test/final",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNotNone(redirected)
|
||||||
|
self.assertEqual("secret", redirected.get_header("X-otrs-header-sessionid"))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Generated
+25
-4
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.30",
|
"version": "0.1.34",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.30",
|
"version": "0.1.34",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@govoplan/access-webui": "file:../../govoplan-access/webui",
|
"@govoplan/access-webui": "file:../../govoplan-access/webui",
|
||||||
"@govoplan/addresses-webui": "file:../../govoplan-addresses/webui",
|
"@govoplan/addresses-webui": "file:../../govoplan-addresses/webui",
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
"@govoplan/tickets-webui": "file:../../govoplan-tickets/webui",
|
"@govoplan/tickets-webui": "file:../../govoplan-tickets/webui",
|
||||||
"@govoplan/views-webui": "file:../../govoplan-views/webui",
|
"@govoplan/views-webui": "file:../../govoplan-views/webui",
|
||||||
"@govoplan/voting-webui": "file:../../govoplan-voting/webui",
|
"@govoplan/voting-webui": "file:../../govoplan-voting/webui",
|
||||||
|
"@govoplan/wiki-webui": "file:../../govoplan-wiki/webui",
|
||||||
"@govoplan/workflow-webui": "file:../../govoplan-workflow/webui",
|
"@govoplan/workflow-webui": "file:../../govoplan-workflow/webui",
|
||||||
"@tiptap/core": "^3.29.2",
|
"@tiptap/core": "^3.29.2",
|
||||||
"@tiptap/extension-image": "^3.29.2",
|
"@tiptap/extension-image": "^3.29.2",
|
||||||
@@ -242,7 +243,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-connectors/webui": {
|
"../../govoplan-connectors/webui": {
|
||||||
"name": "@govoplan/connectors-webui",
|
"name": "@govoplan/connectors-webui",
|
||||||
"version": "0.1.20",
|
"version": "0.1.22",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.18",
|
"@govoplan/core-webui": "^0.1.18",
|
||||||
"react": ">=19.2.7 <20",
|
"react": ">=19.2.7 <20",
|
||||||
@@ -474,7 +475,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-mail/webui": {
|
"../../govoplan-mail/webui": {
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.21",
|
"version": "0.1.22",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
},
|
},
|
||||||
@@ -822,6 +823,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"../../govoplan-wiki/webui": {
|
||||||
|
"name": "@govoplan/wiki-webui",
|
||||||
|
"version": "0.1.20",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@govoplan/core-webui": "^0.1.31",
|
||||||
|
"lucide-react": "^1.23.0",
|
||||||
|
"react": ">=19.2.7 <20",
|
||||||
|
"react-dom": ">=19.2.7 <20",
|
||||||
|
"react-router": ">=8.3.0 <9"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@govoplan/core-webui": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"../../govoplan-workflow/webui": {
|
"../../govoplan-workflow/webui": {
|
||||||
"name": "@govoplan/workflow-webui",
|
"name": "@govoplan/workflow-webui",
|
||||||
"version": "0.1.21",
|
"version": "0.1.21",
|
||||||
@@ -1768,6 +1785,10 @@
|
|||||||
"resolved": "../../govoplan-voting/webui",
|
"resolved": "../../govoplan-voting/webui",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@govoplan/wiki-webui": {
|
||||||
|
"resolved": "../../govoplan-wiki/webui",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@govoplan/workflow-webui": {
|
"node_modules/@govoplan/workflow-webui": {
|
||||||
"resolved": "../../govoplan-workflow/webui",
|
"resolved": "../../govoplan-workflow/webui",
|
||||||
"link": true
|
"link": true
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.30",
|
"version": "0.1.34",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.30",
|
"version": "0.1.34",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.19",
|
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.19",
|
||||||
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.18",
|
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.18",
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"@govoplan/organizations-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git#v0.1.18",
|
"@govoplan/organizations-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git#v0.1.18",
|
||||||
"@govoplan/policy-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#v0.1.18",
|
"@govoplan/policy-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#v0.1.18",
|
||||||
"@govoplan/tickets-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tickets.git#v0.1.20",
|
"@govoplan/tickets-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tickets.git#v0.1.20",
|
||||||
|
"@govoplan/wiki-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-wiki.git#v0.1.20",
|
||||||
"@tiptap/core": "^3.29.2",
|
"@tiptap/core": "^3.29.2",
|
||||||
"@tiptap/extension-image": "^3.29.2",
|
"@tiptap/extension-image": "^3.29.2",
|
||||||
"@tiptap/pm": "^3.29.2",
|
"@tiptap/pm": "^3.29.2",
|
||||||
@@ -1032,6 +1033,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@govoplan/wiki-webui": {
|
||||||
|
"version": "0.1.20",
|
||||||
|
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-wiki.git#66c91351c9eb693ace606c9b69dd5cd804d7531b",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@govoplan/core-webui": "^0.1.31",
|
||||||
|
"lucide-react": "^1.23.0",
|
||||||
|
"react": ">=19.2.7 <20",
|
||||||
|
"react-dom": ">=19.2.7 <20",
|
||||||
|
"react-router": ">=8.3.0 <9"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@govoplan/core-webui": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.30",
|
"version": "0.1.34",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -102,6 +102,7 @@
|
|||||||
"@govoplan/tickets-webui": "file:../../govoplan-tickets/webui",
|
"@govoplan/tickets-webui": "file:../../govoplan-tickets/webui",
|
||||||
"@govoplan/views-webui": "file:../../govoplan-views/webui",
|
"@govoplan/views-webui": "file:../../govoplan-views/webui",
|
||||||
"@govoplan/voting-webui": "file:../../govoplan-voting/webui",
|
"@govoplan/voting-webui": "file:../../govoplan-voting/webui",
|
||||||
|
"@govoplan/wiki-webui": "file:../../govoplan-wiki/webui",
|
||||||
"@govoplan/workflow-webui": "file:../../govoplan-workflow/webui",
|
"@govoplan/workflow-webui": "file:../../govoplan-workflow/webui",
|
||||||
"@tiptap/core": "^3.29.2",
|
"@tiptap/core": "^3.29.2",
|
||||||
"@tiptap/extension-image": "^3.29.2",
|
"@tiptap/extension-image": "^3.29.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.30",
|
"version": "0.1.34",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
"@govoplan/ops-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git#v0.1.18",
|
"@govoplan/ops-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git#v0.1.18",
|
||||||
"@govoplan/policy-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#v0.1.18",
|
"@govoplan/policy-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git#v0.1.18",
|
||||||
"@govoplan/tickets-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tickets.git#v0.1.20",
|
"@govoplan/tickets-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tickets.git#v0.1.20",
|
||||||
|
"@govoplan/wiki-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-wiki.git#v0.1.20",
|
||||||
"@tiptap/core": "^3.29.2",
|
"@tiptap/core": "^3.29.2",
|
||||||
"@tiptap/extension-image": "^3.29.2",
|
"@tiptap/extension-image": "^3.29.2",
|
||||||
"@tiptap/pm": "^3.29.2",
|
"@tiptap/pm": "^3.29.2",
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const packageByModule = {
|
|||||||
tickets: "@govoplan/tickets-webui",
|
tickets: "@govoplan/tickets-webui",
|
||||||
views: "@govoplan/views-webui",
|
views: "@govoplan/views-webui",
|
||||||
voting: "@govoplan/voting-webui",
|
voting: "@govoplan/voting-webui",
|
||||||
|
wiki: "@govoplan/wiki-webui",
|
||||||
workflow: "@govoplan/workflow-webui"
|
workflow: "@govoplan/workflow-webui"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,6 +80,8 @@ const cases = [
|
|||||||
{ name: "forms-runtime", modules: ["forms", "forms_runtime"] },
|
{ name: "forms-runtime", modules: ["forms", "forms_runtime"] },
|
||||||
{ name: "tickets-only", modules: ["tickets"] },
|
{ name: "tickets-only", modules: ["tickets"] },
|
||||||
{ name: "tickets-with-helpdesk-and-cases", modules: ["tickets", "helpdesk", "cases"] },
|
{ name: "tickets-with-helpdesk-and-cases", modules: ["tickets", "helpdesk", "cases"] },
|
||||||
|
{ name: "wiki-only", modules: ["wiki"] },
|
||||||
|
{ name: "wiki-with-files-search", modules: ["wiki", "files", "search"] },
|
||||||
{ name: "mail-only", modules: ["mail"] },
|
{ name: "mail-only", modules: ["mail"] },
|
||||||
{ name: "notifications-only", modules: ["notifications"] },
|
{ name: "notifications-only", modules: ["notifications"] },
|
||||||
{ name: "organizations-only", modules: ["organizations"] },
|
{ name: "organizations-only", modules: ["organizations"] },
|
||||||
@@ -110,7 +113,7 @@ const cases = [
|
|||||||
{ name: "tasks-only", modules: ["access", "tasks"] },
|
{ name: "tasks-only", modules: ["access", "tasks"] },
|
||||||
{ name: "tasks-with-contributors", modules: ["access", "approvals", "postbox", "workflow", "dashboard", "tasks"] },
|
{ name: "tasks-with-contributors", modules: ["access", "approvals", "postbox", "workflow", "dashboard", "tasks"] },
|
||||||
{ name: "voting-only", modules: ["access", "voting"] },
|
{ name: "voting-only", modules: ["access", "voting"] },
|
||||||
{ name: "full-product", modules: ["access", "tenancy", "admin", "addresses", "approvals", "policy", "audit", "dashboard", "datasources", "dataflow", "dist_lists", "templates", "workflow", "views", "organizations", "idm", "identity", "identity_trust", "encryption", "cases", "committee", "connectors", "campaigns", "files", "forms", "forms_runtime", "helpdesk", "mail", "notifications", "docs", "ops", "payments", "calendar", "scheduling", "portal", "postbox", "projects", "quick_access", "reporting", "records", "risk_compliance", "search", "tasks", "tickets", "voting"] }
|
{ name: "full-product", modules: ["access", "tenancy", "admin", "addresses", "approvals", "policy", "audit", "dashboard", "datasources", "dataflow", "dist_lists", "templates", "workflow", "views", "organizations", "idm", "identity", "identity_trust", "encryption", "cases", "committee", "connectors", "campaigns", "files", "forms", "forms_runtime", "helpdesk", "mail", "notifications", "docs", "ops", "payments", "calendar", "scheduling", "portal", "postbox", "projects", "quick_access", "reporting", "records", "risk_compliance", "search", "tasks", "tickets", "voting", "wiki"] }
|
||||||
];
|
];
|
||||||
|
|
||||||
const npmExec = process.env.npm_execpath;
|
const npmExec = process.env.npm_execpath;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export type MailProfilePatternRules = Partial<Record<MailProfilePatternKey, stri
|
|||||||
|
|
||||||
export type MailConnectionTestResponse = {
|
export type MailConnectionTestResponse = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
protocol: "smtp" | "imap";
|
protocol: "smtp" | "imap" | "jmap" | "pop3";
|
||||||
host?: string | null;
|
host?: string | null;
|
||||||
port?: number | null;
|
port?: number | null;
|
||||||
security?: MailSecurity | string | null;
|
security?: MailSecurity | string | null;
|
||||||
@@ -52,7 +52,7 @@ export type MailImapFolderResponse = {
|
|||||||
|
|
||||||
export type MailImapFolderListResponse = {
|
export type MailImapFolderListResponse = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
protocol: "imap";
|
protocol: "imap" | "jmap";
|
||||||
host?: string | null;
|
host?: string | null;
|
||||||
port?: number | null;
|
port?: number | null;
|
||||||
security?: MailSecurity | string | null;
|
security?: MailSecurity | string | null;
|
||||||
@@ -69,6 +69,7 @@ export type MailImapFolderListResponse = {
|
|||||||
export const mailProfilePatternKeys = [
|
export const mailProfilePatternKeys = [
|
||||||
"smtp_hosts",
|
"smtp_hosts",
|
||||||
"imap_hosts",
|
"imap_hosts",
|
||||||
|
"jmap_hosts",
|
||||||
"envelope_senders",
|
"envelope_senders",
|
||||||
"from_headers",
|
"from_headers",
|
||||||
"recipient_domains"
|
"recipient_domains"
|
||||||
@@ -82,11 +83,13 @@ export const mailProfilePolicyLimitKeys = [
|
|||||||
"imap_credentials.inherit",
|
"imap_credentials.inherit",
|
||||||
"whitelist.smtp_hosts",
|
"whitelist.smtp_hosts",
|
||||||
"whitelist.imap_hosts",
|
"whitelist.imap_hosts",
|
||||||
|
"whitelist.jmap_hosts",
|
||||||
"whitelist.envelope_senders",
|
"whitelist.envelope_senders",
|
||||||
"whitelist.from_headers",
|
"whitelist.from_headers",
|
||||||
"whitelist.recipient_domains",
|
"whitelist.recipient_domains",
|
||||||
"blacklist.smtp_hosts",
|
"blacklist.smtp_hosts",
|
||||||
"blacklist.imap_hosts",
|
"blacklist.imap_hosts",
|
||||||
|
"blacklist.jmap_hosts",
|
||||||
"blacklist.envelope_senders",
|
"blacklist.envelope_senders",
|
||||||
"blacklist.from_headers",
|
"blacklist.from_headers",
|
||||||
"blacklist.recipient_domains"
|
"blacklist.recipient_domains"
|
||||||
|
|||||||
+13
-3
@@ -847,6 +847,16 @@ export type MailImapTransportSettings = MailTransportSettings & {
|
|||||||
folder_mappings?: MailImapFolderMappings | null;
|
folder_mappings?: MailImapFolderMappings | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MailJmapTransportSettings = {
|
||||||
|
session_url: string;
|
||||||
|
account_id?: string | null;
|
||||||
|
auth_scheme?: "bearer" | "basic";
|
||||||
|
timeout_seconds?: number | null;
|
||||||
|
max_response_bytes?: number | null;
|
||||||
|
max_body_value_bytes?: number | null;
|
||||||
|
allowed_api_origins?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type MailServerProfileCredentials = {
|
export type MailServerProfileCredentials = {
|
||||||
smtp?: MailTransportCredentials | null;
|
smtp?: MailTransportCredentials | null;
|
||||||
imap?: MailTransportCredentials | null;
|
imap?: MailTransportCredentials | null;
|
||||||
@@ -883,9 +893,9 @@ export type MailServerEndpoint = {
|
|||||||
id: string;
|
id: string;
|
||||||
profile_id: string;
|
profile_id: string;
|
||||||
tenant_id?: string | null;
|
tenant_id?: string | null;
|
||||||
protocol: "smtp" | "imap";
|
protocol: "smtp" | "imap" | "jmap" | "pop3";
|
||||||
name: string;
|
name: string;
|
||||||
config: MailTransportSettings | MailImapTransportSettings;
|
config: MailTransportSettings | MailImapTransportSettings | MailJmapTransportSettings;
|
||||||
scope_type: MailProfileScope;
|
scope_type: MailProfileScope;
|
||||||
scope_id?: string | null;
|
scope_id?: string | null;
|
||||||
inherit_to_lower_scopes: boolean;
|
inherit_to_lower_scopes: boolean;
|
||||||
@@ -922,7 +932,7 @@ export type MailCredentialPolicy = {
|
|||||||
allow_override?: boolean | null;
|
allow_override?: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MailProfilePatternKey = "smtp_hosts" | "imap_hosts" | "envelope_senders" | "from_headers" | "recipient_domains";
|
export type MailProfilePatternKey = "smtp_hosts" | "imap_hosts" | "jmap_hosts" | "envelope_senders" | "from_headers" | "recipient_domains";
|
||||||
|
|
||||||
export type MailProfilePolicy = {
|
export type MailProfilePolicy = {
|
||||||
allowed_profile_ids?: string[] | null;
|
allowed_profile_ids?: string[] | null;
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const defaultWebModulePackages = [
|
|||||||
"@govoplan/tickets-webui",
|
"@govoplan/tickets-webui",
|
||||||
"@govoplan/views-webui",
|
"@govoplan/views-webui",
|
||||||
"@govoplan/voting-webui",
|
"@govoplan/voting-webui",
|
||||||
|
"@govoplan/wiki-webui",
|
||||||
"@govoplan/workflow-webui"
|
"@govoplan/workflow-webui"
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -289,6 +290,7 @@ export default defineConfig({
|
|||||||
fileURLToPath(new URL('../../govoplan-tickets/webui', import.meta.url)),
|
fileURLToPath(new URL('../../govoplan-tickets/webui', import.meta.url)),
|
||||||
fileURLToPath(new URL('../../govoplan-views/webui', import.meta.url)),
|
fileURLToPath(new URL('../../govoplan-views/webui', import.meta.url)),
|
||||||
fileURLToPath(new URL('../../govoplan-voting/webui', import.meta.url)),
|
fileURLToPath(new URL('../../govoplan-voting/webui', import.meta.url)),
|
||||||
|
fileURLToPath(new URL('../../govoplan-wiki/webui', import.meta.url)),
|
||||||
fileURLToPath(new URL('../../govoplan-workflow/webui', import.meta.url))
|
fileURLToPath(new URL('../../govoplan-workflow/webui', import.meta.url))
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user