Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/rest-apis/devportal/labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ This operation requires <strong>Basic Auth</strong> authentication.

</aside>

<h3 id="list-labels-parameters">Parameters</h3>

|Name|In|Type|Required|Description|
|---|---|---|---|---|
|limit|query|integer|false|Maximum number of records to return.|
|offset|query|integer|false|Number of records to skip before returning results.|

> Example responses

> 200 Response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ region = "us"
[[auth.file_based.users]]
username = "admin"
password_hash = "$2y$10$U2yKMwGamGwDoMu0hRPT7u8nCuP8z/qxHFOKV6dhIxkJN9NJ0eVQ."
scopes = "ap:organization:manage ap:gateway:manage ap:gateway_custom_policy:manage ap:rest_api:manage ap:llm_provider:manage ap:llm_proxy:manage ap:mcp_proxy:manage ap:webbroker_api:manage ap:websub_api:manage ap:application:manage ap:subscription:manage ap:subscription_plan:manage ap:project:manage ap:llm_template:manage ap:devportal:manage ap:git:read ap:api_key:read dp:org_read dp:org_write dp:org_manage dp:org_delete dp:org_content_read dp:org_content_write dp:org_content_manage dp:org_content_delete dp:api_read dp:api_write dp:api_manage dp:api_delete dp:api_content_read dp:api_content_write dp:api_content_manage dp:api_content_delete dp:api_key_read dp:api_key_write dp:api_key_manage dp:api_key_revoke dp:api_flow_read dp:api_flow_write dp:api_flow_manage dp:api_flow_delete dp:api_workflow_read dp:api_workflow_create dp:api_workflow_update dp:api_workflow_delete dp:api_workflow_manage dp:app_read dp:app_write dp:app_manage dp:app_delete dp:app_key_write dp:app_key_manage dp:app_key_revoke dp:app_key_mapping_read dp:app_key_mapping_write dp:app_key_mapping_manage dp:subscription_read dp:subscription_write dp:subscription_manage dp:subscription_delete dp:sub_plan_read dp:sub_plan_write dp:sub_plan_manage dp:sub_plan_delete dp:idp_read dp:idp_write dp:idp_manage dp:idp_delete dp:view_read dp:view_write dp:view_manage dp:view_delete dp:km_read dp:km_write dp:km_manage dp:km_delete dp:label_read dp:label_write dp:label_manage dp:label_delete dp:provider_read dp:provider_write dp:provider_manage dp:provider_delete dp:event_read dp:delivery_manage dp:utility_write dp:utility_manage dp:webhook_subscriber_create dp:webhook_subscriber_read dp:webhook_subscriber_update dp:webhook_subscriber_delete dp:webhook_subscriber_manage dev"
scopes = "ap:organization:manage ap:gateway:manage ap:gateway_custom_policy:manage ap:rest_api:manage ap:llm_provider:manage ap:llm_proxy:manage ap:mcp_proxy:manage ap:webbroker_api:manage ap:websub_api:manage ap:application:manage ap:subscription:manage ap:subscription_plan:manage ap:project:manage ap:llm_template:manage ap:devportal:manage ap:git:read ap:api_key:read dp:org_read dp:org_write dp:org_manage dp:org_delete dp:org_content_read dp:org_content_write dp:org_content_manage dp:org_content_delete dp:api_read dp:api_write dp:api_manage dp:api_delete dp:api_content_read dp:api_content_write dp:api_content_manage dp:api_content_delete dp:mcp_read dp:mcp_create dp:mcp_update dp:mcp_manage dp:mcp_delete dp:mcp_content_read dp:mcp_content_create dp:mcp_content_update dp:mcp_content_manage dp:mcp_content_delete dp:mcp_key_read dp:mcp_key_create dp:mcp_key_update dp:mcp_key_manage dp:mcp_key_revoke dp:api_key_read dp:api_key_write dp:api_key_manage dp:api_key_revoke dp:api_flow_read dp:api_flow_write dp:api_flow_manage dp:api_flow_delete dp:api_workflow_read dp:api_workflow_create dp:api_workflow_update dp:api_workflow_delete dp:api_workflow_manage dp:app_read dp:app_write dp:app_manage dp:app_delete dp:app_key_write dp:app_key_manage dp:app_key_revoke dp:app_key_mapping_read dp:app_key_mapping_write dp:app_key_mapping_manage dp:subscription_read dp:subscription_write dp:subscription_manage dp:subscription_delete dp:sub_plan_read dp:sub_plan_write dp:sub_plan_manage dp:sub_plan_delete dp:idp_read dp:idp_write dp:idp_manage dp:idp_delete dp:view_read dp:view_write dp:view_manage dp:view_delete dp:km_read dp:km_write dp:km_manage dp:km_delete dp:label_read dp:label_write dp:label_manage dp:label_delete dp:provider_read dp:provider_write dp:provider_manage dp:provider_delete dp:event_read dp:delivery_manage dp:utility_write dp:utility_manage dp:webhook_subscriber_create dp:webhook_subscriber_read dp:webhook_subscriber_update dp:webhook_subscriber_delete dp:webhook_subscriber_manage dev"

# ---------------------------------------------------------------------------
# TLS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,9 @@ paths:
summary: List labels
description: Returns all labels configured for the organization.
operationId: listLabels
parameters:
- $ref: "#/components/parameters/limit"
- $ref: "#/components/parameters/offset"
responses:
"200":
$ref: "#/components/responses/LabelsResponse"
Expand Down
37 changes: 22 additions & 15 deletions portals/developer-portal/it/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,33 +43,40 @@ ensure-test-tag:
# Requires the developer-portal image to be built first: make build (from portals/developer-portal/).
# Service list is explicit — the compose file also defines rest-api-tests, which
# `up` with no args would otherwise start too.
# Each target captures the exit code of `docker compose up` itself — which
# `--exit-code-from <svc>` sets to that service's container exit code, and which
# is also non-zero when a dependency fails to start — then always tears down and
# re-exits with it. Do NOT recover the code via `docker compose ps -q <svc> |
# docker inspect`: `ps -q` lists only RUNNING containers, so once the service has
# exited it returns nothing and the code is silently lost (the target then passes
# even when tests fail). Keep the capture in the same shell as `up`.
test: ensure-test-tag
-CYPRESS_PLATFORM=$(CYPRESS_PLATFORM) DOCKER_REGISTRY=$(DOCKER_REGISTRY) docker compose -f docker-compose.test.yaml up devportal cypress --abort-on-container-exit --exit-code-from cypress
$(MAKE) _collect-exit COMPOSE_FILE=docker-compose.test.yaml SERVICE=cypress
@CYPRESS_PLATFORM=$(CYPRESS_PLATFORM) DOCKER_REGISTRY=$(DOCKER_REGISTRY) docker compose -f docker-compose.test.yaml up devportal cypress --abort-on-container-exit --exit-code-from cypress; \
EXIT=$$?; \
docker compose -f docker-compose.test.yaml down -v --remove-orphans; \
exit $$EXIT

# Run Cypress tests headlessly against PostgreSQL.
# Requires the developer-portal image to be built first: make build (from portals/developer-portal/).
test-postgres: ensure-test-tag
-CYPRESS_PLATFORM=$(CYPRESS_PLATFORM) DOCKER_REGISTRY=$(DOCKER_REGISTRY) docker compose -f docker-compose.test.postgres.yaml up postgres devportal cypress --abort-on-container-exit --exit-code-from cypress
$(MAKE) _collect-exit COMPOSE_FILE=docker-compose.test.postgres.yaml SERVICE=cypress
@CYPRESS_PLATFORM=$(CYPRESS_PLATFORM) DOCKER_REGISTRY=$(DOCKER_REGISTRY) docker compose -f docker-compose.test.postgres.yaml up postgres devportal cypress --abort-on-container-exit --exit-code-from cypress; \
EXIT=$$?; \
docker compose -f docker-compose.test.postgres.yaml down -v --remove-orphans; \
exit $$EXIT

# Run the REST API integration test suite (Jest + Supertest) against SQLite.
# Requires the developer-portal image to be built first: make build (from portals/developer-portal/).
test-rest-api: ensure-test-tag
-DOCKER_REGISTRY=$(DOCKER_REGISTRY) docker compose -f docker-compose.test.yaml up devportal rest-api-tests --abort-on-container-exit --exit-code-from rest-api-tests
$(MAKE) _collect-exit COMPOSE_FILE=docker-compose.test.yaml SERVICE=rest-api-tests
@DOCKER_REGISTRY=$(DOCKER_REGISTRY) docker compose -f docker-compose.test.yaml up devportal rest-api-tests --abort-on-container-exit --exit-code-from rest-api-tests; \
EXIT=$$?; \
docker compose -f docker-compose.test.yaml down -v --remove-orphans; \
exit $$EXIT

# Run the REST API integration test suite (Jest + Supertest) against PostgreSQL.
test-rest-api-postgres: ensure-test-tag
-DOCKER_REGISTRY=$(DOCKER_REGISTRY) docker compose -f docker-compose.test.postgres.yaml up postgres devportal rest-api-tests --abort-on-container-exit --exit-code-from rest-api-tests
$(MAKE) _collect-exit COMPOSE_FILE=docker-compose.test.postgres.yaml SERVICE=rest-api-tests

# Capture the exit code from the given service container even after compose exits.
COMPOSE_FILE ?= docker-compose.test.yaml
SERVICE ?= cypress
_collect-exit:
@EXIT=$$(docker compose -f $(COMPOSE_FILE) ps -q $(SERVICE) | xargs docker inspect --format='{{.State.ExitCode}}' 2>/dev/null || echo 0); \
docker compose -f $(COMPOSE_FILE) down -v --remove-orphans; \
@DOCKER_REGISTRY=$(DOCKER_REGISTRY) docker compose -f docker-compose.test.postgres.yaml up postgres devportal rest-api-tests --abort-on-container-exit --exit-code-from rest-api-tests; \
EXIT=$$?; \
docker compose -f docker-compose.test.postgres.yaml down -v --remove-orphans; \
exit $$EXIT

# Open Cypress interactive UI — runs against a LOCALLY running devportal (not in Docker).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,22 @@ region = "us"
[[auth.file_based.users]]
username = "admin"
password_hash = "$2y$10$U2yKMwGamGwDoMu0hRPT7u8nCuP8z/qxHFOKV6dhIxkJN9NJ0eVQ."
scopes = "dp:org_read dp:org_create dp:org_update dp:org_manage dp:org_delete dp:org_content_read dp:org_content_write dp:org_content_manage dp:org_content_delete dp:api_read dp:api_create dp:api_update dp:api_manage dp:api_delete dp:api_content_read dp:api_content_write dp:api_content_manage dp:api_content_delete dp:mcp_read dp:mcp_create dp:mcp_update dp:mcp_manage dp:mcp_delete dp:api_key_create dp:api_key_read dp:api_key_update dp:api_key_manage dp:api_key_revoke dp:mcp_key_create dp:mcp_key_read dp:mcp_key_update dp:mcp_key_manage dp:mcp_key_revoke dp:api_workflow_create dp:api_workflow_read dp:api_workflow_update dp:api_workflow_delete dp:api_workflow_manage dp:app_create dp:app_read dp:app_update dp:app_manage dp:app_delete dp:app_key_create dp:app_key_manage dp:app_key_revoke dp:app_key_mapping_read dp:app_key_mapping_write dp:app_key_mapping_manage dp:subscription_create dp:subscription_read dp:subscription_update dp:subscription_manage dp:subscription_delete dp:sub_plan_create dp:sub_plan_read dp:sub_plan_update dp:sub_plan_manage dp:sub_plan_delete dp:km_create dp:km_read dp:km_update dp:km_manage dp:km_delete dp:view_create dp:view_read dp:view_update dp:view_manage dp:view_delete dp:label_create dp:label_read dp:label_update dp:label_manage dp:label_delete dp:webhook_subscriber_create dp:webhook_subscriber_read dp:webhook_subscriber_update dp:webhook_subscriber_delete dp:webhook_subscriber_manage dp:event_read dp:delivery_manage"
scopes = "dp:org_read dp:org_create dp:org_update dp:org_manage dp:org_delete dp:org_content_read dp:org_content_write dp:org_content_manage dp:org_content_delete dp:api_read dp:api_create dp:api_update dp:api_manage dp:api_delete dp:api_content_read dp:api_content_write dp:api_content_manage dp:api_content_delete dp:mcp_read dp:mcp_create dp:mcp_update dp:mcp_manage dp:mcp_delete dp:mcp_content_read dp:mcp_content_create dp:mcp_content_update dp:mcp_content_manage dp:mcp_content_delete dp:api_key_create dp:api_key_read dp:api_key_update dp:api_key_manage dp:api_key_revoke dp:mcp_key_create dp:mcp_key_read dp:mcp_key_update dp:mcp_key_manage dp:mcp_key_revoke dp:api_workflow_create dp:api_workflow_read dp:api_workflow_update dp:api_workflow_delete dp:api_workflow_manage dp:app_create dp:app_read dp:app_update dp:app_manage dp:app_delete dp:app_key_create dp:app_key_manage dp:app_key_revoke dp:app_key_mapping_read dp:app_key_mapping_write dp:app_key_mapping_manage dp:subscription_create dp:subscription_read dp:subscription_update dp:subscription_manage dp:subscription_delete dp:sub_plan_create dp:sub_plan_read dp:sub_plan_update dp:sub_plan_manage dp:sub_plan_delete dp:km_create dp:km_read dp:km_update dp:km_manage dp:km_delete dp:view_create dp:view_read dp:view_update dp:view_manage dp:view_delete dp:label_create dp:label_read dp:label_update dp:label_manage dp:label_delete dp:webhook_subscriber_create dp:webhook_subscriber_read dp:webhook_subscriber_update dp:webhook_subscriber_delete dp:webhook_subscriber_manage dp:event_read dp:delivery_manage"

# Content/config manager — APIs, MCP servers, key managers, subscription
# plans, views/labels, webhook subscribers, API workflows. No org management,
# no application ownership (that's the developer's).
[[auth.file_based.users]]
username = "publisher"
password_hash = "$2y$10$BN9I5oPs34clNmhlO0CX0uDMKMnh9xkczGGmuLiInXSe/KOF5wqFW"
scopes = "dp:org_read dp:api_read dp:api_create dp:api_update dp:api_manage dp:api_delete dp:api_content_read dp:api_content_write dp:api_content_manage dp:api_content_delete dp:mcp_read dp:mcp_create dp:mcp_update dp:mcp_manage dp:mcp_delete dp:api_key_create dp:api_key_read dp:api_key_update dp:api_key_manage dp:api_key_revoke dp:mcp_key_create dp:mcp_key_read dp:mcp_key_update dp:mcp_key_manage dp:mcp_key_revoke dp:api_workflow_create dp:api_workflow_read dp:api_workflow_update dp:api_workflow_delete dp:api_workflow_manage dp:sub_plan_create dp:sub_plan_read dp:sub_plan_update dp:sub_plan_manage dp:sub_plan_delete dp:km_create dp:km_read dp:km_update dp:km_manage dp:km_delete dp:view_create dp:view_read dp:view_update dp:view_manage dp:view_delete dp:label_create dp:label_read dp:label_update dp:label_manage dp:label_delete dp:webhook_subscriber_create dp:webhook_subscriber_read dp:webhook_subscriber_update dp:webhook_subscriber_delete dp:webhook_subscriber_manage dp:event_read"
scopes = "dp:org_read dp:api_read dp:api_create dp:api_update dp:api_manage dp:api_delete dp:api_content_read dp:api_content_write dp:api_content_manage dp:api_content_delete dp:mcp_read dp:mcp_create dp:mcp_update dp:mcp_manage dp:mcp_delete dp:mcp_content_read dp:mcp_content_create dp:mcp_content_update dp:mcp_content_manage dp:mcp_content_delete dp:api_key_create dp:api_key_read dp:api_key_update dp:api_key_manage dp:api_key_revoke dp:mcp_key_create dp:mcp_key_read dp:mcp_key_update dp:mcp_key_manage dp:mcp_key_revoke dp:api_workflow_create dp:api_workflow_read dp:api_workflow_update dp:api_workflow_delete dp:api_workflow_manage dp:sub_plan_create dp:sub_plan_read dp:sub_plan_update dp:sub_plan_manage dp:sub_plan_delete dp:km_create dp:km_read dp:km_update dp:km_manage dp:km_delete dp:view_create dp:view_read dp:view_update dp:view_manage dp:view_delete dp:label_create dp:label_read dp:label_update dp:label_manage dp:label_delete dp:webhook_subscriber_create dp:webhook_subscriber_read dp:webhook_subscriber_update dp:webhook_subscriber_delete dp:webhook_subscriber_manage dp:event_read"

# Portal end-user — read APIs/MCP servers, own applications, subscriptions,
# and API keys. No org, key-manager, view/label, or webhook-subscriber management.
[[auth.file_based.users]]
username = "developer"
password_hash = "$2y$10$jX3o2E5jF4i3EOgoyJ0k.uegbDYmsmFNDfIxnvcZgTNJifAPjgKKK"
scopes = "dp:org_read dp:api_read dp:api_content_read dp:mcp_read dp:api_key_create dp:api_key_read dp:api_key_update dp:api_key_manage dp:api_key_revoke dp:app_create dp:app_read dp:app_update dp:app_manage dp:app_delete dp:app_key_create dp:app_key_manage dp:app_key_revoke dp:app_key_mapping_read dp:app_key_mapping_write dp:app_key_mapping_manage dp:subscription_create dp:subscription_read dp:subscription_update dp:subscription_manage dp:subscription_delete dp:sub_plan_read dp:view_read dp:label_read"
scopes = "dp:org_read dp:api_read dp:api_content_read dp:mcp_read dp:mcp_content_read dp:api_key_create dp:api_key_read dp:api_key_update dp:api_key_manage dp:api_key_revoke dp:app_create dp:app_read dp:app_update dp:app_manage dp:app_delete dp:app_key_create dp:app_key_manage dp:app_key_revoke dp:app_key_mapping_read dp:app_key_mapping_write dp:app_key_mapping_manage dp:subscription_create dp:subscription_read dp:subscription_update dp:subscription_manage dp:subscription_delete dp:sub_plan_read dp:view_read dp:label_read"

[tls]
cert_dir = "/app/data/certs"
Expand Down
12 changes: 7 additions & 5 deletions portals/developer-portal/it/docker-compose.test.postgres.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ services:
entrypoint: ["/bin/sh", "-c"]
command:
- |
[ -f /certs/cert.pem ] && [ -f /certs/key.pem ] && exit 0
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
-keyout /certs/key.pem -out /certs/cert.pem \
-subj "/O=WSO2 API Platform/CN=platform-api" \
-addext "subjectAltName=DNS:localhost,DNS:platform-api,IP:127.0.0.1"
if [ ! -f /certs/cert.pem ] || [ ! -f /certs/key.pem ]; then
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
-keyout /certs/key.pem -out /certs/cert.pem \
-subj "/O=WSO2 API Platform/CN=platform-api" \
-addext "subjectAltName=DNS:localhost,DNS:platform-api,IP:127.0.0.1"
fi
chown 10001:10001 /certs/cert.pem /certs/key.pem
volumes:
- platform-api-certs:/certs

Expand Down
12 changes: 7 additions & 5 deletions portals/developer-portal/it/docker-compose.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ services:
entrypoint: ["/bin/sh", "-c"]
command:
- |
[ -f /certs/cert.pem ] && [ -f /certs/key.pem ] && exit 0
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
-keyout /certs/key.pem -out /certs/cert.pem \
-subj "/O=WSO2 API Platform/CN=platform-api" \
-addext "subjectAltName=DNS:localhost,DNS:platform-api,IP:127.0.0.1"
if [ ! -f /certs/cert.pem ] || [ ! -f /certs/key.pem ]; then
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
-keyout /certs/key.pem -out /certs/cert.pem \
-subj "/O=WSO2 API Platform/CN=platform-api" \
-addext "subjectAltName=DNS:localhost,DNS:platform-api,IP:127.0.0.1"
fi
chown 10001:10001 /certs/cert.pem /certs/key.pem
volumes:
- platform-api-certs:/certs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async function uploadContent(role, handle, zip, { method = 'post', imageMetadata
const req = method === 'put'
? client.as(role).putMultipart(`/apis/${handle}/assets`)
: client.as(role).postMultipart(`/apis/${handle}/assets`);
req.attach('apiContent', zip, 'content.zip');
req.attach('content', zip, 'content.zip');
if (imageMetadata) req.field('imageMetadata', JSON.stringify(imageMetadata));
return req;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,9 @@ describe('APIs via artifact ZIP upload', () => {
// The specific scenario worth checking on its own: create via zip, then re-upload
// essentially the same zip with one small field changed. Both the metadata handle
// (metadata.name in the YAML, not spec.displayName) and the API's own `id` must
// stay stable across the round-trip — apiDao.update() only recomputes the handle
// from name+version when the request supplies no handle at all; the YAML path
// always supplies one via `metadata.name`, so unlike the JSON metadata field
// path this doesn't risk drifting the id/handle as a side effect of the update.
// stay stable across the round-trip — apiDao.update() never touches the handle
// (it is immutable after creation), so no update path can
// drift the id/handle as a side effect.
it('creates via zip then updates the same API with the same zip plus a small change', async () => {
const { handle, zip: firstZip } = buildZip({ displayName: 'Stable Zip API', description: 'version one' });
const create = await client.as('publisher').postMultipart('/apis').attach('artifact', firstZip, 'artifact.zip');
Expand Down
Loading
Loading