diff --git a/README.md b/README.md index 99551c9..d04ccb3 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,10 @@ npm run build 代码仓库和 Release 已公开,Linux 服务器不需要 Gitea Token。安装器读取 `https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest`,生产机不执行 `git pull` 或现场编译。 安装脚本内置固定的发布公钥指纹,下载的应用包、清单、SBOM 和更新脚本仍须通过 RSA 签名与 SHA-256 校验。 +Gitea API 只用于确认 Release tag 和资产名称;下载地址由受信 API 域名、仓库路径和 tag 重新构造,不采用 +Gitea 响应中可能错误指向内网地址的 `browser_download_url`。 本版只支持由运维人员预先准备的外部 MySQL 8.4.x;安装器不会安装 MySQL、创建数据库容器或修改现有 -PostgreSQL 18。PostgreSQL 兼容开发已冻结,不属于本次 Preview.10 发布范围。 +PostgreSQL 18。PostgreSQL 兼容开发已冻结,不属于本次 Preview.11 发布范围。 执行命令的机器需预装 `bash`、`sudo`、`curl`、`mktemp` 和 `sha256sum`,并能访问目标 Gitea;`jq`、Java 和数据库客户端由安装器补齐。应用固定安装到 `/opt/kaidi`、`/var/lib/kaidi`、 @@ -56,14 +58,14 @@ PostgreSQL 18。PostgreSQL 兼容开发已冻结,不属于本次 Preview.10 ### 直接 curl 安装 ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh | sudo bash +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.11/install.sh | sudo bash ``` 这条命令会提示填写 Java 应用端口,直接回车使用 `18080`;随后安装最新签名 Release,并默认进入 `/setup` 安装向导。Java 默认只监听 `127.0.0.1:所选端口`,前端页面、API 和健康检查均由同一端口提供。无人值守安装可直接指定: ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh \ +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.11/install.sh \ | sudo env KAIDI_APP_PORT=19090 bash ``` @@ -71,8 +73,8 @@ curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-previe 时才设置 `KAIDI_SERVER_ADDRESS=0.0.0.0`,通常应保持默认回环绑定并由本机反向代理访问。需要在执行前独立校验安装脚本时使用: ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh -o /tmp/kaidi-install.sh -printf '%s %s\n' cc9ae48fc3e36fbeb13b0660b0d91e59bd099a1386d49df699f214285c1afc0a /tmp/kaidi-install.sh | sha256sum -c - +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.11/install.sh -o /tmp/kaidi-install.sh +printf '%s %s\n' 78bd7a89acc71fc016d1c55816abd466173194bfd71a8f095c73f6deb86babd9 /tmp/kaidi-install.sh | sha256sum -c - sudo bash /tmp/kaidi-install.sh rm -f /tmp/kaidi-install.sh ``` @@ -83,7 +85,7 @@ rm -f /tmp/kaidi-install.sh 包装器会在 `sudo` 前校验 `deploy/install.sh` 的固定 SHA-256,再按同一公钥信任链安装最新签名 Release。 ```bash -git clone --branch v1.0.0-preview.10 --depth 1 https://git.awaioi.com/ERP-Team/kaidi.git kaidi-preview +git clone --branch v1.0.0-preview.11 --depth 1 https://git.awaioi.com/ERP-Team/kaidi.git kaidi-preview cd kaidi-preview ./deploy/install-from-git.sh ``` @@ -138,7 +140,7 @@ sudo cat /root/kaidi-first-login.txt 32 位服务端镜像,因此 32 位主机需要预先连接一台 MySQL 8.4 数据库,之后仍然只执行一个安装命令: ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh | sudo bash +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.11/install.sh | sudo bash ``` 32 位主机不能运行安装器自动创建的 MySQL 容器,因此在打开向导前,需要预先创建 `kaidi_finance`,并授予安装账号该库的 @@ -158,7 +160,7 @@ GRANT ALL PRIVILEGES ON kaidi_finance.* TO 'kaidi'@'KAIDI_SERVER_IP'; 管理员数据和运维人员新增的环境变量: ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh \ +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.11/install.sh \ | sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=false bash ``` @@ -168,7 +170,7 @@ curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-previe 如果安装器已完成但向导尚未提交,可执行下面的命令重新生成一次性安装码;该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。 ```bash -curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.10/install.sh \ +curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.11/install.sh \ | sudo env KAIDI_REINSTALL=true bash ``` @@ -204,8 +206,8 @@ act_runner 提供 `ubuntu-24.04` 标签,并在 tag 发布时执行后端、前 Preview 属性由 SemVer 版本名表达。之后推送 tag 即会构建、测试、签名并发布: ```bash -git tag v1.0.0-preview.10 -git push origin v1.0.0-preview.10 +git tag v1.0.0-preview.11 +git push origin v1.0.0-preview.11 ``` 在线更新使用独立的 TDesign 页面:隔离的系统管理员进入“系统治理 → 系统更新”。权限与配置页只管理用户、角色、数据范围、表单模板和参数版本,不配置系统名称或域名。 @@ -256,7 +258,7 @@ cat /var/lib/kaidi-update/status.json ```bash KAIDI_RELEASE_SIGNING_KEY=/secure/release-signing-private.pem \ KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \ - ./scripts/package-release.sh 1.0.0-preview.10 + ./scripts/package-release.sh 1.0.0-preview.11 KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \ ./scripts/verify-release.sh dist/release ``` diff --git a/backend/src/main/java/com/kaidi/finance/update/application/SystemUpdateApplicationService.java b/backend/src/main/java/com/kaidi/finance/update/application/SystemUpdateApplicationService.java index 039d4f0..f2a5d4a 100644 --- a/backend/src/main/java/com/kaidi/finance/update/application/SystemUpdateApplicationService.java +++ b/backend/src/main/java/com/kaidi/finance/update/application/SystemUpdateApplicationService.java @@ -15,6 +15,7 @@ import com.kaidi.finance.update.api.SystemUpdateView; import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -52,6 +53,9 @@ public class SystemUpdateApplicationService { + "(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$"); private static final Pattern ARTIFACT = Pattern.compile("^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}\\.tar\\.gz$"); private static final Pattern SHA256 = Pattern.compile("^[0-9a-fA-F]{64}$"); + private static final Pattern GITEA_RELEASE_API_PATH = Pattern.compile( + "^(.*/)?api/v1/repos/([A-Za-z0-9._-]+)/([A-Za-z0-9._-]+)/releases/(?:latest|tags/[^/?#]+)$"); + private static final Pattern GITEA_RELEASE_TAG = Pattern.compile("^v[0-9A-Za-z][0-9A-Za-z._+-]{0,127}$"); private static final List BUSY_STATES = List.of( "QUEUED", "DOWNLOAD_QUEUED", "INSTALL_QUEUED", "VERIFYING", "DOWNLOADING", "BACKING_UP", "INSTALLING", "RUNNING"); @@ -195,20 +199,42 @@ public class SystemUpdateApplicationService { URI api = releaseApiUri(); try { JsonNode release = objectMapper.readTree(fetchReleaseBytes(api, MAX_RELEASE_API_BYTES, "Gitea Release")); + boolean assetPresent = false; for (JsonNode asset : release.path("assets")) { - if (!assetName.equals(asset.path("name").asText())) continue; - URI uri = validatedReleaseUri(asset.path("browser_download_url").asText(null), "Release 资源地址"); - if (!sameOrigin(api, uri)) { - throw releaseUnavailable("Gitea Release 资源必须与 API 使用同一来源"); + if (assetName.equals(asset.path("name").asText())) { + assetPresent = true; + break; } - return uri; } - throw releaseUnavailable("Gitea Release 缺少 " + assetName); + if (!assetPresent) throw releaseUnavailable("Gitea Release 缺少 " + assetName); + String tag = release.path("tag_name").asText(null); + if (tag == null || !GITEA_RELEASE_TAG.matcher(tag).matches()) { + throw releaseUnavailable("Gitea Release tag 格式无效"); + } + return trustedGiteaAssetUri(api, tag, assetName); } catch (IOException exception) { throw releaseUnavailable("Gitea Release 响应读取失败"); } } + private URI trustedGiteaAssetUri(URI api, String tag, String assetName) { + if (api.getRawQuery() != null || api.getRawFragment() != null) { + throw releaseUnavailable("Gitea Release API 地址格式无效"); + } + Matcher matcher = GITEA_RELEASE_API_PATH.matcher(api.getPath()); + if (!matcher.matches()) { + throw releaseUnavailable("Gitea Release API 地址必须指向仓库 Release 端点"); + } + String prefix = matcher.group(1) == null ? "/" : matcher.group(1); + String path = prefix + matcher.group(2) + "/" + matcher.group(3) + + "/releases/download/" + tag + "/" + assetName; + try { + return new URI(api.getScheme(), null, api.getHost(), api.getPort(), path, null, null); + } catch (URISyntaxException exception) { + throw releaseUnavailable("Gitea Release 资源地址格式无效"); + } + } + private byte[] fetchReleaseBytes(URI uri, int maximumBytes, String resourceName) { String token = releaseToken(); URI current = uri; diff --git a/backend/src/test/java/com/kaidi/finance/update/application/SystemUpdateApplicationServiceTest.java b/backend/src/test/java/com/kaidi/finance/update/application/SystemUpdateApplicationServiceTest.java index 0980446..7cfc300 100644 --- a/backend/src/test/java/com/kaidi/finance/update/application/SystemUpdateApplicationServiceTest.java +++ b/backend/src/test/java/com/kaidi/finance/update/application/SystemUpdateApplicationServiceTest.java @@ -47,18 +47,17 @@ class SystemUpdateApplicationServiceTest { HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); server.createContext("/api/v1/repos/ERP-Team/kaidi/releases/latest", exchange -> { assertEquals("token read-only-token", exchange.getRequestHeaders().getFirst("Authorization")); - String assetUrl = "http://127.0.0.1:" + server.getAddress().getPort() - + "/assets/release-manifest.json"; byte[] body = (""" {"tag_name":"v1.0.0-preview.2","assets":[ {"name":"release-manifest.json","browser_download_url":"%s"} ]} - """).formatted(assetUrl).getBytes(StandardCharsets.UTF_8); + """).formatted("http://10.0.0.8:3000/internal/release-manifest.json") + .getBytes(StandardCharsets.UTF_8); exchange.sendResponseHeaders(200, body.length); exchange.getResponseBody().write(body); exchange.close(); }); - server.createContext("/assets/release-manifest.json", exchange -> { + server.createContext("/ERP-Team/kaidi/releases/download/v1.0.0-preview.2/release-manifest.json", exchange -> { assertEquals("token read-only-token", exchange.getRequestHeaders().getFirst("Authorization")); byte[] body = """ {"version":"1.0.0-preview.2","artifact":"kaidi-finance-1.0.0-preview.2.tar.gz", @@ -98,7 +97,7 @@ class SystemUpdateApplicationServiceTest { } @Test - void rejectsPrivateGiteaAssetOnAnotherOriginWithoutSendingToken() throws Exception { + void ignoresCrossOriginBrowserAssetUrlWithoutSendingTokenThere() throws Exception { AtomicInteger assetRequests = new AtomicInteger(); HttpServer assetServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); assetServer.createContext("/release-manifest.json", exchange -> { @@ -112,12 +111,25 @@ class SystemUpdateApplicationServiceTest { String assetUrl = "http://127.0.0.1:" + assetServer.getAddress().getPort() + "/release-manifest.json"; byte[] body = (""" - {"assets":[{"name":"release-manifest.json","browser_download_url":"%s"}]} + {"tag_name":"v1.0.0-preview.2", + "assets":[{"name":"release-manifest.json","browser_download_url":"%s"}]} """).formatted(assetUrl).getBytes(StandardCharsets.UTF_8); exchange.sendResponseHeaders(200, body.length); exchange.getResponseBody().write(body); exchange.close(); }); + apiServer.createContext( + "/ERP-Team/kaidi/releases/download/v1.0.0-preview.2/release-manifest.json", exchange -> { + assertEquals("token read-only-token", exchange.getRequestHeaders().getFirst("Authorization")); + byte[] body = """ + {"version":"1.0.0-preview.2","artifact":"kaidi-finance-1.0.0-preview.2.tar.gz", + "sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "publishedAt":"2026-08-16T00:00:00Z","releaseNotes":"Gitea Preview update"} + """.getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(200, body.length); + exchange.getResponseBody().write(body); + exchange.close(); + }); assetServer.start(); apiServer.start(); try { @@ -125,7 +137,7 @@ class SystemUpdateApplicationServiceTest { SystemUpdateApplicationService service = serviceForGitea(apiServer, inbox, tempDir.resolve("cross-origin-status.json")); - assertThrows(BusinessException.class, service::check); + assertEquals("1.0.0-preview.2", service.check().latestVersion()); assertEquals(0, assetRequests.get()); } finally { apiServer.stop(0); diff --git a/deploy/install-from-git.sh b/deploy/install-from-git.sh index 7e95555..a4f3405 100755 --- a/deploy/install-from-git.sh +++ b/deploy/install-from-git.sh @@ -5,7 +5,7 @@ umask 077 ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) INSTALLER="$ROOT/deploy/install.sh" -INSTALLER_SHA256=${KAIDI_INSTALLER_SHA256:-cc9ae48fc3e36fbeb13b0660b0d91e59bd099a1386d49df699f214285c1afc0a} +INSTALLER_SHA256=${KAIDI_INSTALLER_SHA256:-78bd7a89acc71fc016d1c55816abd466173194bfd71a8f095c73f6deb86babd9} RELEASE_API_URL=${KAIDI_RELEASE_API_URL:-https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest} PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9} TOKEN_FILE=${KAIDI_RELEASE_TOKEN_FILE:-} diff --git a/deploy/install.sh b/deploy/install.sh index 45dae3a..1f6362f 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -126,17 +126,20 @@ download_release_url() { } release_asset_url() { - local asset_name=$1 asset_url api_origin + local asset_name=$1 api_origin repo_path release_tag encoded_tag encoded_asset if [ -n "$RELEASE_API_URL" ]; then - asset_url=$(jq -er --arg name "$asset_name" \ - '.assets[] | select(.name == $name) | .browser_download_url - | strings | select(startswith("https://") or startswith("http://"))' \ - "$WORK_DIR/release-api.json" | head -n 1) || die "Release asset $asset_name is missing" + jq -e --arg name "$asset_name" 'any(.assets[]?; .name == $name)' \ + "$WORK_DIR/release-api.json" >/dev/null || die "Release asset $asset_name is missing" + release_tag=$(jq -er '.tag_name | strings + | select(test("^v[0-9A-Za-z][0-9A-Za-z._+-]{0,127}$"))' \ + "$WORK_DIR/release-api.json") || die "Gitea Release tag is invalid" api_origin=$(printf '%s' "$RELEASE_API_URL" | sed -E 's#^(https?://[^/]+).*$#\1#') - case "$asset_url" in - "$api_origin"/*) printf '%s\n' "$asset_url" ;; - *) die "Release asset $asset_name must use the Gitea API host" ;; - esac + repo_path=$(printf '%s' "$RELEASE_API_URL" | sed -nE \ + 's#^https?://[^/]+/api/v1/repos/([A-Za-z0-9._-]+/[A-Za-z0-9._-]+)/releases/(latest|tags/[^/?#]+)$#\1#p') + [ -n "$repo_path" ] || die "KAIDI_RELEASE_API_URL must be a Gitea repository Release API endpoint" + encoded_tag=$(jq -rn --arg value "$release_tag" '$value | @uri') + encoded_asset=$(jq -rn --arg value "$asset_name" '$value | @uri') + printf '%s/%s/releases/download/%s/%s\n' "$api_origin" "$repo_path" "$encoded_tag" "$encoded_asset" else printf '%s/%s\n' "${RELEASE_BASE_URL%/}" "$asset_name" fi diff --git a/deploy/update.sh b/deploy/update.sh index 969272a..456e5af 100755 --- a/deploy/update.sh +++ b/deploy/update.sh @@ -124,15 +124,18 @@ download() { release_asset_url() { asset_name=$1 if [ -n "$RELEASE_API_URL" ]; then - asset_url=$(jq -er --arg name "$asset_name" \ - '.assets[] | select(.name == $name) | .browser_download_url - | strings | select(startswith("https://") or startswith("http://"))' \ - "$WORK_DIR/release-api.json" | head -n 1) || return 1 + jq -e --arg name "$asset_name" 'any(.assets[]?; .name == $name)' \ + "$WORK_DIR/release-api.json" >/dev/null || return 1 + release_tag=$(jq -er '.tag_name | strings + | select(test("^v[0-9A-Za-z][0-9A-Za-z._+-]{0,127}$"))' \ + "$WORK_DIR/release-api.json") || return 1 api_origin=$(printf '%s' "$RELEASE_API_URL" | sed -E 's#^(https?://[^/]+).*$#\1#') - case "$asset_url" in - "$api_origin"/*) printf '%s\n' "$asset_url" ;; - *) return 1 ;; - esac + repo_path=$(printf '%s' "$RELEASE_API_URL" | sed -nE \ + 's#^https?://[^/]+/api/v1/repos/([A-Za-z0-9._-]+/[A-Za-z0-9._-]+)/releases/(latest|tags/[^/?#]+)$#\1#p') + [ -n "$repo_path" ] || return 1 + encoded_tag=$(jq -rn --arg value "$release_tag" '$value | @uri') + encoded_asset=$(jq -rn --arg value "$asset_name" '$value | @uri') + printf '%s/%s/releases/download/%s/%s\n' "$api_origin" "$repo_path" "$encoded_tag" "$encoded_asset" else printf '%s/%s\n' "${RELEASE_BASE_URL%/}" "$asset_name" fi diff --git a/scripts/test-install-fixture.sh b/scripts/test-install-fixture.sh index 2ba9eb1..d40d288 100755 --- a/scripts/test-install-fixture.sh +++ b/scripts/test-install-fixture.sh @@ -165,7 +165,7 @@ RELEASE_AUTH_HEADER_FILE=$WORK/release-auth-header printf 'Authorization: token %s\n' "$RELEASE_TOKEN" > "$RELEASE_AUTH_HEADER_FILE" chmod 0600 "$RELEASE_AUTH_HEADER_FILE" cat > "$WORK/release-api.json" <<'JSON' -{"assets":[ +{"tag_name":"v1.0.0-preview.11","assets":[ {"name":"release-manifest.json","browser_download_url":"https://gitea.fixture.invalid/assets/release-manifest.json"} ]} JSON @@ -188,12 +188,13 @@ curl() { done [ "$header_file" = "$RELEASE_AUTH_HEADER_FILE" ] || return 90 grep -Fqx "Authorization: token $RELEASE_TOKEN" "$header_file" || return 91 - [ "$url" = https://gitea.fixture.invalid/assets/release-manifest.json ] || return 92 + [ "$url" = https://gitea.fixture.invalid/ERP-Team/kaidi/releases/download/v1.0.0-preview.11/release-manifest.json ] \ + || return 92 cp "$WORK/release-manifest.fixture" "$output" } [ "$(release_asset_url release-manifest.json)" = \ - https://gitea.fixture.invalid/assets/release-manifest.json ] \ - || fail 'installer did not resolve the private Gitea release asset' + https://gitea.fixture.invalid/ERP-Team/kaidi/releases/download/v1.0.0-preview.11/release-manifest.json ] \ + || fail 'installer did not construct the trusted Gitea release asset URL' download_release_asset release-manifest.json "$WORK/downloaded-manifest.json" cmp -s "$WORK/release-manifest.fixture" "$WORK/downloaded-manifest.json" \ || fail 'installer did not download the private Gitea release asset' @@ -205,9 +206,9 @@ cmp -s "$WORK/release-manifest.fixture" "$WORK/downloaded-manifest.json" \ jq '.assets[0].browser_download_url = "https://assets.fixture.invalid/release-manifest.json"' \ "$WORK/release-api.json" > "$WORK/release-api.cross-origin.json" mv "$WORK/release-api.cross-origin.json" "$WORK/release-api.json" -if release_asset_url release-manifest.json >/dev/null 2>&1; then - fail 'installer accepted a cross-origin Gitea release asset' -fi +[ "$(release_asset_url release-manifest.json)" = \ + https://gitea.fixture.invalid/ERP-Team/kaidi/releases/download/v1.0.0-preview.11/release-manifest.json ] \ + || fail 'installer trusted the cross-origin browser download URL' # shellcheck disable=SC2016 # Match literal installer source. grep -Fq '[ "$APP_ROOT" = /opt/kaidi ]' "$ROOT/deploy/install.sh" \ diff --git a/scripts/test-update-fixture.sh b/scripts/test-update-fixture.sh index 5fb316b..e85f753 100755 --- a/scripts/test-update-fixture.sh +++ b/scripts/test-update-fixture.sh @@ -154,10 +154,12 @@ build_release() { build_gitea_release_index() { local fixture=$1 local origin=${2:-https://gitea.fixture.invalid} - local artifact + local artifact version tag artifact=$(jq -er '.artifact' "$fixture/release/release-manifest.json") - jq -n --arg origin "$origin" --arg artifact "$artifact" \ - '{assets:[ + version=$(jq -er '.version' "$fixture/release/release-manifest.json") + tag="v$version" + jq -n --arg origin "$origin" --arg artifact "$artifact" --arg tag "$tag" \ + '{tag_name:$tag,assets:[ {name:"release-manifest.json",browser_download_url:($origin + "/assets/release-manifest.json")}, {name:"release-manifest.sig",browser_download_url:($origin + "/assets/release-manifest.sig")}, {name:$artifact,browser_download_url:($origin + "/assets/" + $artifact)} @@ -267,7 +269,7 @@ assert_private_gitea_release_case() { || fail 'authenticated private Gitea requests unexpectedly enabled redirects' } -assert_cross_origin_gitea_asset_rejected() { +assert_cross_origin_gitea_browser_url_ignored() { local fixture="$WORK/cross-origin-gitea" local version='1.0.0-preview.2' mkdir -p "$fixture" @@ -276,18 +278,16 @@ assert_cross_origin_gitea_asset_rejected() { build_gitea_release_index "$fixture" https://assets.fixture.invalid prepare_installation "$fixture" "$version" - if FIXTURE_RELEASE_BASE_URL='' \ + FIXTURE_RELEASE_BASE_URL='' \ FIXTURE_RELEASE_API_URL=https://gitea.fixture.invalid/api/v1/repos/ERP-Team/kaidi/releases/latest \ FIXTURE_RELEASE_ORIGIN=https://gitea.fixture.invalid \ FIXTURE_RELEASE_TOKEN=fixture-read-only-token \ MOCK_CURL_LOG="$fixture/curl.log" \ - run_update "$fixture" success > "$fixture/update.log" 2>&1; then - fail 'cross-origin Gitea asset unexpectedly succeeded' - fi - grep -q 'Release manifest asset is missing' "$fixture/update.log" \ - || fail 'cross-origin Gitea asset rejection was not reported' + run_update "$fixture" success + [ "$(jq -r '.state' "$fixture/state/status.json")" = READY ] \ + || fail 'misconfigured browser download URL prevented trusted same-origin download' ! grep -Fq 'assets.fixture.invalid' "$fixture/curl.log" \ - || fail 'cross-origin Gitea asset was requested' + || fail 'cross-origin browser download URL was requested' } assert_success_case() { @@ -430,5 +430,5 @@ assert_database_failure_case assert_symlink_request_rejected assert_install_without_verified_cache_rejected assert_private_gitea_release_case -assert_cross_origin_gitea_asset_rejected +assert_cross_origin_gitea_browser_url_ignored printf 'Online update download, confirmation, success, rollback, failure, and unsafe-request fixtures passed\n'