diff --git a/.env.local b/.env.local index 11f701031..7703c9e8c 100644 --- a/.env.local +++ b/.env.local @@ -1,4 +1,4 @@ -LOG_LEVEL=info +LOG_LEVEL=debug DISABLE_LOGGING='true' AUTH_STRATEGY=Oauth2Proxy KNEX_HOST=kong-db @@ -27,7 +27,26 @@ LOCAL_ENV=true WORKING_PATH=/tmp DESTINATION_URL= SSR_API_ROOT=http://apsportal.localtest.me:3000 +NEXT_PUBLIC_APP_VERSION=0.0.0 +NEXT_PUBLIC_APP_REVISION=000000000000000000000000 +NEXT_PUBLIC_KUBE_CLUSTER=local +NEXT_PUBLIC_HELP_DESK_URL=https://dpdd.atlassian.net/servicedesk/customer/portal/1/group/2 +NEXT_PUBLIC_HELP_CHAT_URL=https://chat.developer.gov.bc.ca/channel/aps-ops +NEXT_PUBLIC_HELP_ISSUE_URL=https://github.com/bcgov/api-services-portal/issues +NEXT_PUBLIC_HELP_API_DOCS_URL=/ds/api/v3/console/ +NEXT_PUBLIC_HELP_SUPPORT_URL=https://dev.developer.gov.bc.ca/docs/default/component/aps-infra-platform-docs/ +NEXT_PUBLIC_HELP_RELEASE_URL=https://dev.developer.gov.bc.ca/docs/default/component/aps-infra-platform-docs/reference/releases/ +NEXT_PUBLIC_HELP_STATUS_URL=https://uptime.com/s/bcgov-dss NEXT_PUBLIC_DEVELOPER_IDS=idir,bceid,bcsc,github NEXT_PUBLIC_PROVIDER_IDS=idir NEXT_PUBLIC_ACCOUNT_BCEID_URL=https://www.test.bceid.ca/logon.aspx?returnUrl=/profile_management NEXT_PUBLIC_ACCOUNT_BCSC_URL=https://idtest.gov.bc.ca/account/ +NEXT_PUBLIC_GRAFANA_URL=https://grafana.local + + +# For automated integrated testing +TEST_PORTAL_CLIENT_ID=aps-portal +TEST_PORTAL_CLIENT_SECRET=8e1a17ed-cb93-4806-ac32-e303d1c86018 +TEST_PORTAL_USERNAME=janis@idir +TEST_PORTAL_PASSWORD=awsummer + diff --git a/.github/workflows/aps-cypress-e2e.yaml b/.github/workflows/aps-cypress-e2e.yaml index 1f8a82cfe..8a8046f89 100644 --- a/.github/workflows/aps-cypress-e2e.yaml +++ b/.github/workflows/aps-cypress-e2e.yaml @@ -58,14 +58,20 @@ jobs: fi done - - name: Upload E2E Test Results Report - uses: actions/upload-artifact@v2 + - name: Upload E2E Test Results HTML Report + uses: actions/upload-artifact@v4 with: - name: test-results + name: test-results-html path: ${{ github.workspace }}/e2e/results/report + - name: Upload E2E Test Results JSON Report + uses: actions/upload-artifact@v4 + with: + name: test-results-json + path: ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json + - name: Upload E2E Code Coverage Report - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: code-coverage path: ${{ github.workspace }}/e2e/coverage @@ -100,8 +106,16 @@ jobs: run: | FAILURE_COUNT=$(cat ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json | jq '.stats.failures') if [[ "$FAILURE_COUNT" -gt 0 ]]; then - FAILED_TESTS=$(cat ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json | jq '.results | .[] | .suites | .[].tests | .[] | select(.fail==true) | .title') - STATS=$(cat ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json | jq '.stats') + FAILED_TESTS=$(jq -r ' + .results[] | + (.file | split("/") | .[2:] | join("/")) as $file | + .. | + .tests? // empty | + .[] | + select(.fail == true) | + "- " + $file + " - " + .title + ' ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json) + STATS=$(cat ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json | jq -r '.stats | to_entries | map("\(.key)\t\(.value)") | .[]' | column -t) echo -e "Stats: $STATS\n\nFailed Tests:\n$FAILED_TESTS\n\nRun Link: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" > msg export MSG=$(cat msg) gh issue create --title "FAILED: Automated Tests($FAILURE_COUNT)" --body "$MSG" --label "automation" --assignee "${{ env.GIT_COMMIT_AUTHOR }}" diff --git a/.github/workflows/ci-build-deploy.yaml b/.github/workflows/ci-build-deploy.yaml index a4abaa718..4f9aa28a8 100644 --- a/.github/workflows/ci-build-deploy.yaml +++ b/.github/workflows/ci-build-deploy.yaml @@ -262,7 +262,7 @@ jobs: NEXT_PUBLIC_HELP_ISSUE_URL: value: 'https://github.com/bcgov/api-services-portal/issues' NEXT_PUBLIC_HELP_API_DOCS_URL: - value: '/ds/api/v2/console/' + value: '/ds/api/v3/console/' NEXT_PUBLIC_HELP_SUPPORT_URL: value: 'https://developer.gov.bc.ca/docs/default/component/aps-infra-platform-docs/' NEXT_PUBLIC_HELP_RELEASE_URL: diff --git a/.github/workflows/ci-feat-sonar.yaml b/.github/workflows/ci-feat-sonar.yaml index 34ccb59ea..efcf4a03a 100644 --- a/.github/workflows/ci-feat-sonar.yaml +++ b/.github/workflows/ci-feat-sonar.yaml @@ -23,7 +23,7 @@ jobs: - name: Run Tests run: | - docker compose up kong-db -d + docker compose up keycloak -d set -o allexport source ./.env.local diff --git a/README.md b/README.md index f0748a572..62427ec00 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Run this command to test logging in and creating a namespace: ``` gwa login -gwa namespace create --name gw-12345 +gwa gateway create --gateway-id gw-12345 ``` ### Keycloak configuration @@ -67,17 +67,19 @@ Use the following configuration to run the Portal locally (outside of Docker) ag 1. If using Node version > 17, run `npm install --legacy-peer-deps` -1. Turn off the docker compose Portal: `docker stop apsportal` -1. Configure the `oauth2-proxy` that is running in Docker: +1. Turn off the docker compose Portal and OAuth2 Proxy: `docker stop apsportal oauth2-proxy` - 1. Update `upstreams` in `local/oauth2-proxy/oauth2-proxy-local.cfg` to include the IP address of your local machine, e.g. `upstreams=["http://172.100.100.01:3000"]` -
You can obtain the IP address using `hostname -I`. +1. Start the OAuth2 Proxy locally: - 1. Restart the oauth2-proxy: `docker compose restart oauth2-proxy` - 1. Update `DESTINATION_URL` in `local/feeds/.env.local` to include the IP address of your local machine - 1. Restart the feeder: `docker compose restart feeder` - 1. Update `PORTAL_ACTIVITY_URL` in `local/gwa-api/.env.local` to include the IP address of your local machine - 1. Restart the feeder: `docker compose restart gwa-api` +```sh +hostip=$(ifconfig en0 | awk '$1 == "inet" {print $2}') + +docker run -ti --rm --name proxy --net=host \ + --add-host portal.localtest.me:$hostip \ + -v `pwd`/local/oauth2-proxy/oauth2-proxy-dev.cfg:/oauth2.config \ + quay.io/oauth2-proxy/oauth2-proxy:v7.2.0 \ + --config /oauth2.config +``` 1. Start the Portal locally: diff --git a/e2e/Dockerfile b/e2e/Dockerfile index 432a9cd5a..2a093d736 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -22,7 +22,7 @@ COPY e2e/*.yml /e2e COPY e2e/entrypoint.sh /tmp ADD e2e/cypress /e2e/cypress -RUN curl -v -L -O https://github.com/bcgov/gwa-cli/releases/download/v2.0.15/gwa_Linux_x86_64.tgz \ +RUN curl -v -L -O https://github.com/bcgov/gwa-cli/releases/download/v3.0.4/gwa_Linux_x86_64.tgz \ && tar -xzf gwa_Linux_x86_64.tgz \ && mv gwa /usr/local/bin/. diff --git a/e2e/cypress.config.ts b/e2e/cypress.config.ts index 2cf4bc2a3..43b2102f1 100644 --- a/e2e/cypress.config.ts +++ b/e2e/cypress.config.ts @@ -38,6 +38,8 @@ export default defineConfig({ './cypress/tests/16-*/*.ts', './cypress/tests/17-*/*.ts', './cypress/tests/18-*/*.ts', + './cypress/tests/19-*/*.ts', + './cypress/tests/20-*/*.ts', ] return config }, @@ -57,9 +59,10 @@ export default defineConfig({ }, chromeWebSecurity: false, env: { + ASTRA_SCAN_ENABLED: true, CLIENT_ID: 'aps-portal', CLIENT_SECRET: '8e1a17ed-cb93-4806-ac32-e303d1c86018', - OIDC_ISSUER: 'http://keycloak.localtest.me:9081', + OIDC_ISSUER: 'http://keycloak.localtest.me:9081/auth/realms/master', TOKEN_URL: 'http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/token', GWA_API_URL: 'http://gwa-api.localtest.me:2000/v2', @@ -69,6 +72,8 @@ export default defineConfig({ BASE_URL: 'http://oauth2proxy.localtest.me:4180', KEYCLOAK_URL: 'http://keycloak.localtest.me:9081', WEBAPP_URL: 'http://html-sample-app.localtest.me:4242', + DEV_USERNAME: 'janis@idir', + DEV_PASSWORD: 'awsummer', }, retries: { runMode: 2, diff --git a/e2e/cypress/fixtures/access-manager.json b/e2e/cypress/fixtures/access-manager.json index 6ada07e69..7a5c3ea8c 100644 --- a/e2e/cypress/fixtures/access-manager.json +++ b/e2e/cypress/fixtures/access-manager.json @@ -13,7 +13,7 @@ "namespace": "permission" }, "serviceAccount": { - "scopes": ["GatewayConfig.Publish", "Namespace.Manage", "Content.Publish"] + "scopes": ["GatewayConfig.Publish", "Gateway.Manage", "Content.Publish"] }, "labels_consumer1": { "labels": { diff --git a/e2e/cypress/fixtures/api-v2.json b/e2e/cypress/fixtures/api-v2.json new file mode 100644 index 000000000..91dbdac97 --- /dev/null +++ b/e2e/cypress/fixtures/api-v2.json @@ -0,0 +1,398 @@ +{ + "organization": { + "headers": { + "accept": "application/json", + "content-type": "application/json" + }, + "endPoint": "ds/api/v2/organizations", + "orgExpectedList": { + "name": "planning-and-innovation-division", + "title": "Planning and Innovation Division" + }, + "orgName": "ministry-of-health", + "expectedScope": [ + "Dataset.Manage", + "GroupAccess.Manage", + "Namespace.Assign" + ], + "expectedNamespace": { + "name": "gw-3a443", + "orgUnit": "planning-and-innovation-division", + "enabled": true + }, + "expectedRoles": { + "organization-admin": { + "label": "Organization Administrator", + "permissions": [ + { + "resourceType": "organization", + "scopes": [ + "GroupAccess.Manage", + "Namespace.Assign", + "Dataset.Manage" + ] + }, + { + "resourceType": "namespace", + "scopes": [ + "Namespace.View" + ] + } + ] + } + }, + "body": { + "name": "ministry-of-health", + "parent": "/ca.bc.gov", + "members": [ + { + "member": { + "id": "janis@idir", + "email": "janis@testmail.com" + }, + "roles": [ + "organization-admin" + ] + } + ] + } + }, + "documentation": { + "endPoint": "ds/api/v2/namespaces/apiplatform/contents", + "getDocumentation_endPoint": "ds/api/v2/documentation", + "headers": { + "accept": "application/json", + "content-type": "application/json" + }, + "body": { + "externalLink": "https://externalsite/my_content", + "title": "my_content", + "description": "Summary of Test content", + "content": "Markdown content", + "order": 0, + "isPublic": true, + "isComplete": true, + "tags": [ + "tag1", + "tag2" + ] + } + }, + "apiDirectory": { + "endPoint": "ds/api/v2/namespaces", + "orgEndPoint": "ds/api/v2/organizations", + "directoryEndPoint": "ds/api/v2/directory", + "headers": { + "accept": "application/json", + "content-type": "application/json" + }, + "body": { + "name": "auto-test-product-new", + "license_title": "Open Government Licence - British Columbia", + "security_class": "PUBLIC", + "view_audience": "Government", + "download_audience": "Public", + "notes": "Some notes", + "title": "Dataset for Test API", + "isInCatalog": "false", + "isDraft": "false", + "tags": [ + "gateway", + "kong" + ], + "organization": "ministry-of-health", + "organizationUnit": "planning-and-innovation-division" + }, + "directory": { + "name": "gwa-auto-test-product", + "title": "GWA Auto Test Product", + "notes": "For Test Purpose", + "license_title": "Open Government Licence - British Columbia", + "view_audience": "Public", + "security_class": "PUBLIC", + "record_publish_date": "2017-09-05", + "tags": [ + "tag1", + "tag2" + ], + "organization": { + "name": "ministry-of-health", + "title": "Ministry of Health" + }, + "organizationUnit": { + "name": "planning-and-innovation-division", + "title": "Planning and Innovation Division" + }, + "products": [ + { + "id": "2", + "name": "GWA Auto Test Product", + "environments": [ + { + "name": "dev", + "active": true, + "flow": "kong-api-key-only" + }, + { + "name": "test", + "active": true, + "flow": "client-credentials" + } + ] + } + ] + }, + "namespaceDirectory": { + "name": "client-credentials-test-product", + "title": "Client Credentials Test Product", + "notes": "API Gateway Services provides a way to configure services on the API Gateway, manage access to APIs and get insight into the use of them.", + "license_title": "Access Only", + "view_audience": "Government", + "security_class": "LOW-PUBLIC", + "tags": [ + "gateway", + "kong", + "openapi" + ], + "organization": { + "name": "ministry-of-health", + "title": "Ministry of Health" + }, + "organizationUnit": { + "name": "planning-and-innovation-division", + "title": "Planning and Innovation Division" + }, + "products": [ + { + "id": "2", + "name": "Client Credentials Test Product", + "environments": [ + { + "name": "test", + "active": true, + "flow": "client-credentials" + }, + { + "name": "dev", + "active": true, + "flow": "client-credentials" + }, + { + "name": "sandbox", + "active": true, + "flow": "client-credentials" + } + ] + } + ] + } + }, + "authorizationProfiles": { + "body": { + "name": "my-auth-profile", + "description": "Auth connection to my IdP", + "flow": "client-credentials", + "clientAuthenticator": "client-secret", + "mode": "auto", + "environmentDetails": [ + { + "environment": "dev", + "issuerUrl": "http://keycloak.localtest.me:9081/auth/realms/master", + "clientRegistration": "managed", + "clientId": "cypress-auth-profile", + "clientSecret": "43badfc1-c06f-4bec-bab6-ccdc764071ac" + } + ], + "owner": "janis@idir" + }, + "shared_IDP_update_body": { + "name": "Sample Shared IdP", + "description": "A Shared IdP for Teams to use", + "flow": "client-credentials", + "clientAuthenticator": "client-secret", + "mode": "auto", + "environmentDetails": [ + { + "environment": "test", + "issuerUrl": "http://keycloak.localtest.me:9081/auth/realms/master", + "clientRegistration": "managed", + "clientId": "gwa-api", + "clientSecret": "18900468-3db1-43f7-a8af-e75f079eb742" + } + ], + "isShared": true + }, + "shared_gwa": { + "name": "Gold Shared IdP", + "description": "A Shared IdP for Teams to use", + "flow": "client-credentials", + "clientAuthenticator": "client-secret", + "mode": "auto", + "environmentDetails": [ + { + "environment": "test", + "issuerUrl": "http://keycloak.localtest.me:9081/auth/realms/master", + "clientRegistration": "managed", + "clientId": "gwa-api", + "clientSecret": "18900468-3db1-43f7-a8af-e75f079eb742" + } + ], + "isShared": true + }, + "shared_gwa_publish": { + "name": "Gold Shared IdP", + "description": "A Shared IdP for Teams to use", + "flow": "client-credentials", + "mode": "auto", + "clientAuthenticator": "client-secret", + "environmentDetails": [ + { + "clientId": "gwa-api", + "clientRegistration": "managed", + "clientSecret": "****", + "environment": "test", + "issuerUrl": "http://keycloak.localtest.me:9081/auth/realms/master", + "exists": true + } + ], + "clientRoles": [], + "clientMappers": [], + "availableScopes": [], + "resourceScopes": [], + "isShared": true, + "apiKeyName": "X-API-KEY" + }, + "shared_IDP_body": { + "name": "Sample Shared IdP new", + "description": "A Shared IdP for Teams to use", + "flow": "client-credentials", + "clientAuthenticator": "client-secret", + "mode": "auto", + "environmentDetails": [ + { + "environment": "test", + "issuerUrl": "http://keycloak.localtest.me:9081/auth/realms/master", + "clientRegistration": "managed", + "clientId": "gwa-api", + "clientSecret": "18900468-3db1-43f7-a8af-e75f079eb742" + } + ], + "isShared": true + }, + "shared_IDP_inheritFrom": { + "environmentDetails": [], + "mode": "auto", + "clientRoles": [ + "administrator" + ], + "clientMappers": [ + "test-audience" + ], + "flow": "client-credentials", + "clientAuthenticator": "client-secret", + "name": "my-auth-client-secret-1", + "description": "Auth connection to my IdP", + "owner": "janis@testmail.com", + "inheritFrom": "Sample Shared IdP new" + }, + "shared_IDP_inheritFrom_expectedResponse": { + "name": "my-auth-client-secret-1", + "description": "Auth connection to my IdP", + "flow": "client-credentials", + "mode": "auto", + "clientAuthenticator": "client-secret", + "environmentDetails": [ + { + "exists": true, + "environment": "test", + "issuerUrl": "http://keycloak.localtest.me:9081/auth/realms/master", + "clientRegistration": "shared-idp", + "clientId": "ap-my-auth-client-secret-1-test" + } + ], + "clientRoles": [ + "administrator" + ], + "clientMappers": [ + "test-audience" + ], + "isShared": false, + "apiKeyName": "X-API-KEY", + "inheritFrom": { + "name": "Sample Shared IdP" + }, + "owner": "janis@testmail.com" + }, + "endPoint": "ds/api/v2/namespaces/apiplatform/issuers", + "headers": { + "accept": "application/json", + "content-type": "application/json" + } + }, + "products": { + "headers": { + "accept": "application/json", + "content-type": "application/json" + }, + "endPoint": "ds/api/v2/namespaces/apiplatform/products", + "deleteEnvironmentEndPoint": "ds/api/v2/namespaces/apiplatform/environments", + "body": { + "name": "my-new-product", + "appId": "DRE123456", + "environments": [ + { + "name": "test", + "active": false, + "approval": false, + "flow": "public", + "appId": "6754" + } + ] + } + }, + "namespaces": { + "headers": { + "accept": "application/json", + "content-type": "application/json" + }, + "endPoint": "ds/api/v2/namespaces", + "activity": { + "name": "newplatform", + "scopes": [ + { + "name": "GatewayConfig.Publish" + }, + { + "name": "Namespace.Manage" + }, + { + "name": "Access.Manage" + }, + { + "name": "Content.Publish" + }, + { + "name": "Namespace.View" + }, + { + "name": "CredentialIssuer.Admin" + } + ], + "permDomains": [ + ".api.gov.bc.ca" + ], + "permDataPlane": "local.dataplane", + "permProtectedNs": "deny", + "org": "ministry-of-health", + "orgUnit": "planning-and-innovation-division" + }, + "userDefinedNamespace": { + "name": "gwanelatform", + "displayName": "Test for GWA test" + }, + "inValidNamespace": { + "name": "gwa", + "displayName": "Test for GWA test" + } + } +} \ No newline at end of file diff --git a/e2e/cypress/fixtures/api.json b/e2e/cypress/fixtures/api.json index 91dbdac97..9ef3f2343 100644 --- a/e2e/cypress/fixtures/api.json +++ b/e2e/cypress/fixtures/api.json @@ -4,7 +4,7 @@ "accept": "application/json", "content-type": "application/json" }, - "endPoint": "ds/api/v2/organizations", + "endPoint": "ds/api/v3/organizations", "orgExpectedList": { "name": "planning-and-innovation-division", "title": "Planning and Innovation Division" @@ -35,7 +35,7 @@ { "resourceType": "namespace", "scopes": [ - "Namespace.View" + "Gateway.View" ] } ] @@ -57,31 +57,10 @@ ] } }, - "documentation": { - "endPoint": "ds/api/v2/namespaces/apiplatform/contents", - "getDocumentation_endPoint": "ds/api/v2/documentation", - "headers": { - "accept": "application/json", - "content-type": "application/json" - }, - "body": { - "externalLink": "https://externalsite/my_content", - "title": "my_content", - "description": "Summary of Test content", - "content": "Markdown content", - "order": 0, - "isPublic": true, - "isComplete": true, - "tags": [ - "tag1", - "tag2" - ] - } - }, "apiDirectory": { - "endPoint": "ds/api/v2/namespaces", - "orgEndPoint": "ds/api/v2/organizations", - "directoryEndPoint": "ds/api/v2/directory", + "endPoint": "ds/api/v3/gateways", + "orgEndPoint": "ds/api/v3/gateways", + "directoryEndPoint": "ds/api/v3/directory", "headers": { "accept": "application/json", "content-type": "application/json" @@ -206,7 +185,7 @@ "owner": "janis@idir" }, "shared_IDP_update_body": { - "name": "Sample Shared IdP", + "name": "Sample Shared IdP updated", "description": "A Shared IdP for Teams to use", "flow": "client-credentials", "clientAuthenticator": "client-secret", @@ -323,7 +302,7 @@ }, "owner": "janis@testmail.com" }, - "endPoint": "ds/api/v2/namespaces/apiplatform/issuers", + "endPoint": "ds/api/v3/gateways/apiplatform/issuers", "headers": { "accept": "application/json", "content-type": "application/json" @@ -334,8 +313,8 @@ "accept": "application/json", "content-type": "application/json" }, - "endPoint": "ds/api/v2/namespaces/apiplatform/products", - "deleteEnvironmentEndPoint": "ds/api/v2/namespaces/apiplatform/environments", + "endPoint": "ds/api/v3/gateways/apiplatform/products", + "deleteEnvironmentEndPoint": "ds/api/v3/gateways/apiplatform/environments", "body": { "name": "my-new-product", "appId": "DRE123456", @@ -355,7 +334,7 @@ "accept": "application/json", "content-type": "application/json" }, - "endPoint": "ds/api/v2/namespaces", + "endPoint": "ds/api/v3/gateways", "activity": { "name": "newplatform", "scopes": [ @@ -363,7 +342,7 @@ "name": "GatewayConfig.Publish" }, { - "name": "Namespace.Manage" + "name": "Gateway.Manage" }, { "name": "Access.Manage" @@ -372,7 +351,7 @@ "name": "Content.Publish" }, { - "name": "Namespace.View" + "name": "Gateway.View" }, { "name": "CredentialIssuer.Admin" diff --git a/e2e/cypress/fixtures/apiowner.json b/e2e/cypress/fixtures/apiowner.json index 7d8caa53d..8c36109c9 100644 --- a/e2e/cypress/fixtures/apiowner.json +++ b/e2e/cypress/fixtures/apiowner.json @@ -15,7 +15,7 @@ "serviceAccount": { "scopes": [ "GatewayConfig.Publish", - "Namespace.Manage", + "Gateway.Manage", "Content.Publish" ] }, @@ -316,7 +316,7 @@ "serviceAccount": { "scopes": [ "GatewayConfig.Publish", - "Namespace.Manage" + "Gateway.Manage" ] }, "product": { @@ -345,7 +345,7 @@ "serviceAccount": { "scopes": [ "GatewayConfig.Publish", - "Namespace.Manage" + "Gateway.Manage" ] }, "product": { @@ -361,8 +361,8 @@ } }, "orgAdminNotification": { - "parent": "Your Organization Administrator has been notified to enable API Publishing to the Directory for the orgassignment namespace.", - "child": "New namespaces must be reviewed by your Organization Administrator before you can publish APIs to the Directory. Your APIs are still in preview mode. For status inquiries, contact your Organization Administrator benny@test.com." + "parent": "Your Organization Administrator has been notified to enable API Publishing to the Directory for the gateway orgassignment.", + "child": "New gateways must be reviewed by your Organization Administrator before you can publish APIs to the Directory. Your APIs are still in preview mode. For status inquiries, contact your Organization Administrator benny@test.com." } }, "orgAssignmentMultipleAdmin": { @@ -435,7 +435,7 @@ "serviceAccount": { "scopes": [ "GatewayConfig.Publish", - "Namespace.Manage" + "Gateway.Manage" ] }, "product": { @@ -462,14 +462,14 @@ "userName": "Mark F Mark L", "email": "mark@gmail.com", "accessRole": [ - "Namespace.View" + "Gateway.View" ] }, "Wendy": { "userName": "Wendy F Wendy L", "email": "wendy@test.com", "accessRole": [ - "Namespace.Manage", + "Gateway.Manage", "CredentialIssuer.Admin" ] }, @@ -477,7 +477,7 @@ "userName": "Wendy F Wendy L", "email": "wendy@test.com", "accessRole": [ - "Namespace.Manage" + "Gateway.Manage" ] }, "Wendy_CA": { @@ -492,14 +492,14 @@ "email": "wendy@test.com", "accessRole": [ "GatewayConfig.Publish", - "Namespace.View" + "Gateway.View" ] }, "Janis": { "userName": "Janis Smith", "email": "janis@testmail.com", "accessRole": [ - "Namespace.Manage", + "Gateway.Manage", "CredentialIssuer.Admin" ] } @@ -508,7 +508,7 @@ "Mark": { "userName": "mark", "accessRole": [ - "Namespace.View", + "Gateway.View", "Access.Manage" ] }, @@ -521,7 +521,7 @@ "Wendy": { "userName": "wendy", "accessRole": [ - "Namespace.Manage" + "Gateway.Manage" ] }, "Wendy_ci": { @@ -532,7 +532,7 @@ }, "serviceAccount": { "scopes": [ - "Namespace.Manage", + "Gateway.Manage", "Content.Publish" ] } @@ -544,7 +544,7 @@ "email": "mark@gmail.com", "accessRole": [ "Access.Manage", - "Namespace.View" + "Gateway.View" ] }, "Janis": { @@ -552,7 +552,7 @@ "email": "janis@testmail.com", "accessRole": [ "CredentialIssuer.Admin", - "Namespace.Manage" + "Gateway.Manage" ] }, "OldUser": { @@ -560,8 +560,8 @@ "email": "olduser@testmail.com", "accessRole": [ "Access.Manage", - "Namespace.View", - "Namespace.Manage" + "Gateway.View", + "Gateway.Manage" ] } }, @@ -570,7 +570,7 @@ "userName": "mark", "accessRole": [ "Access.Manage", - "Namespace.View" + "Gateway.View" ] } }, @@ -791,5 +791,11 @@ "name": "dev" } } + }, + "githubUser": { + "credentials": { + "username": "janis@github", + "password": "awsummer" + } } } \ No newline at end of file diff --git a/e2e/cypress/fixtures/common-testdata.json b/e2e/cypress/fixtures/common-testdata.json index 2728113f5..98e24a71f 100644 --- a/e2e/cypress/fixtures/common-testdata.json +++ b/e2e/cypress/fixtures/common-testdata.json @@ -10,7 +10,7 @@ "namespace": "gw-16a3a" }, "orgAssignment": { - "namespace": "gw-f016e" + "namespace": "gw-f6a4d" }, "orgAssignmentMultipleAdmin": { "namespace": "orgassignment1" @@ -32,14 +32,14 @@ "userName": "Mark F Mark L", "email": "mark@gmail.com", "accessRole": [ - "Namespace.View" + "Gateway.View" ] }, "Wendy": { "userName": "Wendy F Wendy L", "email": "wendy@test.com", "accessRole": [ - "Namespace.Manage", + "Gateway.Manage", "CredentialIssuer.Admin" ] }, @@ -47,7 +47,7 @@ "userName": "Wendy F Wendy L", "email": "wendy@test.com", "accessRole": [ - "Namespace.Manage" + "Gateway.Manage" ] }, "Wendy_CA": { @@ -62,14 +62,14 @@ "email": "wendy@test.com", "accessRole": [ "GatewayConfig.Publish", - "Namespace.View" + "Gateway.View" ] }, "Janis": { "userName": "Janis Smith", "email": "janis@testmail.com", "accessRole": [ - "Namespace.Manage", + "Gateway.Manage", "CredentialIssuer.Admin" ] } @@ -81,5 +81,26 @@ }, "twoTieredHidden": { "namespace": "two-tier-hidden" + }, + "serviceAvailability": { + "namespace": "service-avail" + }, + "myGateways": { + "namespace1": { + "gatewayId": "gw-apple", + "displayName": "Apple" + }, + "namespace2": { + "gatewayId": "gw-banana", + "displayName": "Banana" + }, + "namespace3": { + "gatewayId": "gw-date", + "displayName": "Cherry" + }, + "namespace4": { + "gatewayId": "gw-cherry", + "displayName": "Cherry" + } } } \ No newline at end of file diff --git a/e2e/cypress/fixtures/service-availability.yml b/e2e/cypress/fixtures/service-availability.yml new file mode 100644 index 000000000..64851f962 --- /dev/null +++ b/e2e/cypress/fixtures/service-availability.yml @@ -0,0 +1,19 @@ +kind: GatewayService +name: taken-service-name +host: httpbin.org +tags: [ns.service-avail] +port: 446 +protocol: https +retries: 0 +routes: + - name: taken-service-name + tags: [ns.service-avail] + hosts: + - taken-service-name.api.gov.bc.ca + paths: + - / + methods: + - GET + strip_path: false + https_redirect_status_code: 426 + path_handling: v0 diff --git a/e2e/cypress/pageObjects/apiDirectory.ts b/e2e/cypress/pageObjects/apiDirectory.ts index d0aaf834f..7d69a1584 100644 --- a/e2e/cypress/pageObjects/apiDirectory.ts +++ b/e2e/cypress/pageObjects/apiDirectory.ts @@ -1,6 +1,7 @@ class ApiDirectoryPage { path: string = '/devportal/api-directory' + yourProductsPath: string = '/devportal/api-directory/your-products' rqstAccessBtn: string = '[data-testid=request-access-button]' appSelect: string = '[data-testid=access-application-select]' additionalNotes: string = '[data-testid=access-rqst-add-notes-text]' diff --git a/e2e/cypress/pageObjects/home.ts b/e2e/cypress/pageObjects/home.ts index 559257c20..b7092d16c 100644 --- a/e2e/cypress/pageObjects/home.ts +++ b/e2e/cypress/pageObjects/home.ts @@ -1,68 +1,10 @@ class HomePage { - nsDropdown: string = '[data-testid=ns-dropdown-btn]' - nsDropdownCreateNsBtn: string = '[data-testid=ns-dropdown-create-btn]' - nsDropdownManageNsBtn: string = '[data-testid=ns-dropdown-manage-btn]' - namespaceNameInput: string = '[data-testid=ns-modal-name-input]' - nsCreateBtn: string = '[data-testid=ns-modal-create-btn]' - nsSelectNamespace: string = '[data-testid=ns-dropdown-item-]' userMenu: string = '[data-testid=auth-menu-user]' - manageNamespace: string = '[data-testid="ns-dropdown-manage-btn"]' - confirmDeleteNamespaceBtn: string = '[data-testid="confirm-delete-namespace-btn"]' - namespaceCancelBtn: string = '[data-testid="ns-modal-cancel-btn"]' - - createNamespace(name: string): void { - cy.get(this.nsDropdown).click() - cy.get(this.nsDropdownCreateNsBtn).click() - cy.get(this.namespaceNameInput).type(name) // using `platform` as a default ns as its being seeding through feeder - cy.get(this.nsCreateBtn).click() - cy.verifyToastMessage("Namespace "+name+" created!") - cy.wait(5000) // wait for dropdown to have latest text - cy.get(this.nsDropdown).then(($el) => { - expect($el).contain(name) - }) - } - - useNamespace(name: string): Boolean { - var flag = new Boolean(false); - cy.get(this.nsDropdown).click() - cy.get(this.getNamespaceTestId(name)).click() - cy.wait(5000) // wait for dropdown to have latest text - cy.get(this.nsDropdown).then(($el) => { - expect($el.text().trim()).to.eq(name) - flag = true - }) - return flag - } - - getNamespaceTestId(name: string): string { - return '[data-testid=ns-dropdown-item-' + name + ']' - } - - deleteNamespace(name: string) { - cy.get(this.nsDropdown).click() - cy.get(this.manageNamespace).click() - cy.get(`[data-testid=${name}-namespace-delete-btn]`).click() - cy.get(this.confirmDeleteNamespaceBtn).click() - } - - validateNamespaceName(name: any) { - cy.get(this.nsDropdown).click() - cy.get(this.nsDropdownCreateNsBtn).click() - let namespaceName: Array = name - namespaceName.forEach((accessName) => { - cy.get(this.namespaceNameInput).clear() - cy.get(this.namespaceNameInput).type(accessName) - cy.get(this.nsCreateBtn).click() - cy.wait(1000) - cy.get(this.namespaceNameInput).next('div').then(($ele) => { - let validationMessage = $ele.text() - assert.equal(validationMessage,"Namespace name must be between 5 and 15 alpha-numeric lowercase characters and start and end with an alphabet.") - }) - // cy.verifyToastMessage("Namespace create failed") - }) - cy.get(this.namespaceCancelBtn).click() - } - + apiDirectoryNavButtom: string = '[data-testid="navbar-link-API Directory"]' + accessNavButtom: string = '[data-testid="navbar-link-My Access"]' + applicationsNavButtom: string = '[data-testid="navbar-link-Applications"]' + gatewaysNavButtom: string = '[data-testid="navbar-link-Gateways"]' + } export default HomePage diff --git a/e2e/cypress/pageObjects/myProfile.ts b/e2e/cypress/pageObjects/myProfile.ts deleted file mode 100644 index eb34b9f75..000000000 --- a/e2e/cypress/pageObjects/myProfile.ts +++ /dev/null @@ -1,15 +0,0 @@ -class MyProfilePage { - path: string = '/poc/my-profile' - userProfile: string = '[data-testid=my_profile_json]' - - checkScopeOfProfile(expScope : string): void { - var obj: any - let scope : string - cy.get(this.userProfile).then(($ele) => { - obj = JSON.parse($ele.get(0).innerText) - expect(obj.scopes[0]).eq(expScope) - }) - } -} - -export default MyProfilePage \ No newline at end of file diff --git a/e2e/cypress/pageObjects/namespace.ts b/e2e/cypress/pageObjects/namespace.ts index 549e4e449..7835e4612 100644 --- a/e2e/cypress/pageObjects/namespace.ts +++ b/e2e/cypress/pageObjects/namespace.ts @@ -1,11 +1,15 @@ class NameSpacePage { - path: string = '/manager/namespaces' + path: string = '/manager/gateways' + listPath: string = 'manager/gateways/list' + detailPath: string = '/manager/gateways/detail' + getStartedPath: string = '/manager/gateways/get-started' + listFilterSelect: string = '[data-testid="ns-filter-select"]' gatewayServiceLink: string = '[data-testid="ns-manage-link-Gateway Services"]' productsLink: string = '[data-testid="ns-manage-link-Gateway Services"]' consumersLink: string = '[data-testid="ns-manage-link-Consumers"]' activityLink: string = '[data-testid="ns-action-link-Activity"]' authorizationProfileLink: string = '[data-testid="ns-action-link-Authorization Profiles"]' - namespaceAccessLink: string = '[data-testid="ns-action-link-Namespace Access"]' + namespaceAccessLink: string = '[data-testid="ns-action-link-Administration Access"]' serviceAccountsLink: string = '[data-testid="ns-action-link-Service Accounts"]' deleteNamespaceLink: string = '[data-testid="ns-action-link-delete"]' diff --git a/e2e/cypress/pageObjects/namespaceAccess.ts b/e2e/cypress/pageObjects/namespaceAccess.ts index 799914aa3..f493367d9 100644 --- a/e2e/cypress/pageObjects/namespaceAccess.ts +++ b/e2e/cypress/pageObjects/namespaceAccess.ts @@ -79,7 +79,7 @@ class NamespaceAccessPage { }) } - path: string = '/manager/namespace-access' + path: string = '/manager/admin-access' clickGrantUserAccessButton() { cy.wait(3000) diff --git a/e2e/cypress/pageObjects/products.ts b/e2e/cypress/pageObjects/products.ts index e1b1ff727..eab3c1d57 100644 --- a/e2e/cypress/pageObjects/products.ts +++ b/e2e/cypress/pageObjects/products.ts @@ -2,6 +2,7 @@ import { updateYamlDocument } from "@atomist/yaml-updater"; import _ = require("cypress/types/lodash"); const YAML = require('yamljs'); +const { kebabCase } = require('lodash'); class Products { path: string = '/manager/products' @@ -57,7 +58,7 @@ class Products { } editProduct(productName: string) { - const pname: string = productName.toLowerCase().replaceAll(' ', '-') + const pname: string = kebabCase(productName) cy.get(`[data-testid=${pname}-more-options-btn]`).first().click() cy.get(`[data-testid=${pname}-edit-btn]`).first().click() // cy.get(this.updateBtn).click() @@ -92,7 +93,7 @@ class Products { } editProductEnvironment(productName: string, envName: string) { - const pname: string = productName.toLowerCase().replaceAll(' ', '-') + const pname: string = kebabCase(productName) let env = this.getTestIdEnvName(envName); cy.get(`[data-testid=${pname}-${env}-edit-btn]`).click() cy.wait(2000) @@ -228,7 +229,7 @@ class Products { } deleteProductEnvironment(productName: string, envName: string) { - const pname: string = productName.toLowerCase().replaceAll(' ', '-') + const pname: string = kebabCase(productName) let env = this.getTestIdEnvName(envName); cy.get(`[data-testid=${pname}-${env}-more-options-btn]`).click() cy.get(`[data-testid=${pname}-${env}-delete-btn]`).click() @@ -236,8 +237,7 @@ class Products { } deleteProduct(productName: string) { - // this.editProduct(productName) - const pname: string = productName.toLowerCase().replaceAll(' ', '-') + const pname: string = kebabCase(productName) cy.get(`[data-testid=${pname}-edit-btn]`).first().click({ force: true }) cy.get(`[data-testid=${pname}-delete-btn]`).first().click({ force: true }) cy.get(this.deleteProductConfirmationBtn).click() diff --git a/e2e/cypress/support/auth-commands.ts b/e2e/cypress/support/auth-commands.ts index fd289af65..4d040f156 100644 --- a/e2e/cypress/support/auth-commands.ts +++ b/e2e/cypress/support/auth-commands.ts @@ -7,15 +7,15 @@ import { checkElementExists } from './e2e' // import _ = require('cypress/types/lodash') const njwt = require('njwt') -const fs = require('fs'); +const fs = require('fs') const config = require('../fixtures/manage-control/kong-plugin-config.json') const jose = require('node-jose') -const YAML = require('yamljs'); +const YAML = require('yamljs') -const forge = require('node-forge'); +const forge = require('node-forge') let headers: any @@ -61,10 +61,9 @@ Cypress.Commands.add('login', (username: string, password: string, skipFlag = fa } if (!skipFlag) { - cy.get(home.nsDropdown, { timeout: 6000 }).then(($el) => { + cy.get(home.accessNavButtom, { timeout: 6000 }).then(($el) => { expect($el).to.exist expect($el).to.be.visible - expect($el).contain('No Active Namespace') }) cy.log('> Log in') } @@ -79,12 +78,119 @@ Cypress.Commands.add('keycloakLogin', (username: string, password: string) => { cy.get(login.loginSubmitButton).click() }) +Cypress.Commands.add('createGateway', (gatewayid?: string, displayname?: string) => { + cy.log('< Create namespace - ') + const payload = { + gatewayId: gatewayid ? gatewayid : '', + displayName: displayname ? displayname : '', + } + cy.setHeaders({ 'Content-Type': 'application/json' }) + cy.setRequestBody(payload) + return cy.callAPI('ds/api/v3/gateways', 'POST').then( + ({ apiRes: { body, status } }: any) => { + cy.log(JSON.stringify(body, null, 2)) + expect(status).to.be.equal(200) + if (payload.gatewayId) { + expect(body.gatewayId).to.be.equal(payload.gatewayId) + } + if (payload.displayName) { + expect(body.displayName).to.be.equal(payload.displayName) + } + return cy.wrap(body) + } + ) +}) + +Cypress.Commands.add('deleteGatewayCli', (gatewayid: string, force: boolean = false) => { + cy.executeCliCommand('gwa config set gateway ' + gatewayid).then(() => { + cy.executeCliCommand(`gwa gateway destroy ${force ? '--force' : ''}`).then( + (response) => { + console.log(response) + expect(response.stdout).to.contain('Gateway destroyed: ' + gatewayid) + }) + }) +}) + +Cypress.Commands.add('activateGateway', (gatewayId: string, checkNoNamespace: boolean = false) => { + const getAllNsQuery = ` +query GetNamespaces { + allNamespaces { + id + name + } +} +` + const currentNsQuery = ` + query GetCurrentNamespace { + currentNamespace { + name + org + orgUnit + } + } +` + cy.log('< Activating namespace - ' + gatewayId) + // get the (true) id for the namespace + cy.setHeaders({ 'Content-Type': 'application/json' }) + return cy.gqlQuery(getAllNsQuery).then((response) => { + const nsdata = response.apiRes.body.data.allNamespaces.find((ns: { name: string }) => ns.name === gatewayId) + if (nsdata) { + return nsdata.id + } else { + if (checkNoNamespace) { + return 'Namespace not found' + } else { + throw new Error('Namespace not found') + } + } + }).then((namespaceId) => { + if (namespaceId === 'Namespace not found') { + return namespaceId; + } + // then activate the namespace + cy.setHeaders({ 'Content-Type': 'application/json' }) + cy.callAPI(`admin/switch/${namespaceId}`, 'PUT') + return cy.gqlQuery(currentNsQuery).then((response) => { + const currentNs = response.apiRes.body.data.currentNamespace + expect(currentNs.name).to.eq(gatewayId) + }) + }) +}) + +Cypress.Commands.add('getGateways', () => { + const getAllNsQuery = ` +query GetNamespaces { + allNamespaces { + id + name + } +} +` + cy.log('< Getting namespaces - ') + // get the (true) id for the namespace + cy.setHeaders({ 'Content-Type': 'application/json' }) + return cy.gqlQuery(getAllNsQuery).then((response) => { + const allNamespaces = response.apiRes.body.data.allNamespaces + if (allNamespaces) { + return allNamespaces + } else { + throw new Error('Namespaces could not be retrieved') + } + }) +}) + Cypress.Commands.add('getLastConsumerID', () => { let id: any - cy.get('[data-testid="all-consumer-control-tbl"]').find('tr').last().find('td').first().find('a').then(($text) => { - id = $text.text() - return id - }) + cy.get('[data-testid="all-consumer-control-tbl"]') + .find('tr') + .last() + .find('td') + .first() + .find('a') + .then(($text) => { + id = $text.text() + return id + }) }) Cypress.Commands.add('resetCredential', (accessRole: string) => { @@ -101,7 +207,7 @@ Cypress.Commands.add('resetCredential', (accessRole: string) => { cy.get('@common-testdata').then(({ checkPermission }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) + cy.activateGateway(checkPermission.namespace) cy.visit(na.path) na.revokeAllPermission(checkPermission.grantPermission[accessRole].userName) cy.wait(2000) @@ -111,31 +217,34 @@ Cypress.Commands.add('resetCredential', (accessRole: string) => { }) }) -Cypress.Commands.add('getUserSessionTokenValue', (namespace: string, isNamespaceSelected?: true) => { - const login = new LoginPage() - const home = new HomePage() - const na = new NamespaceAccessPage() - let userSession: string - cy.visit('/') - cy.reload() - cy.fixture('apiowner').as('apiowner') - cy.preserveCookies() - cy.visit(login.path) - cy.getUserSession().then(() => { +Cypress.Commands.add( + 'getUserSessionTokenValue', + (namespace: string = 'ns', isNamespaceSelected?: boolean) => { + const login = new LoginPage() + const home = new HomePage() + const na = new NamespaceAccessPage() + let userSession: string + cy.visit('/') + cy.reload() + cy.fixture('apiowner').as('apiowner') + cy.preserveCookies() + cy.visit(login.path) cy.get('@apiowner').then(({ user }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - // home.useNamespace(apiTest.namespace) + // cy.activateGateway(apiTest.namespace) if (isNamespaceSelected || undefined) { - home.useNamespace(namespace) + cy.activateGateway(namespace) } - cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] - return userSession + cy.getUserSession().then(() => { + cy.get('@login').then(function (xhr: any) { + userSession = xhr.headers['x-auth-request-access-token'] + return userSession + }) }) }) - }) -}) + } +) Cypress.Commands.add('getUserSessionResponse', () => { cy.getUserSession().then(() => { @@ -161,41 +270,42 @@ Cypress.Commands.add('getSession', () => { cy.log('> Get Session') }) -Cypress.Commands.add('loginByAuthAPI', (username: string, password: string) => { +Cypress.Commands.add('loginByAuthAPI', (username: string, password: string): any => { const log = Cypress.log({ displayName: 'AUTH0 LOGIN', message: [`🔐 Authenticating | ${username}`], autoEnd: false, }) log.snapshot('before') - cy.request({ - method: 'POST', - url: Cypress.env('OIDC_ISSUER') + '/protocol/openid-connect/token', - body: { - grant_type: 'password', - username: Cypress.env('DEV_USERNAME'), - password: Cypress.env('DEV_PASSWORD'), - Scope: 'openid', - client_id: Cypress.env('CLIENT_ID'), - client_secret: Cypress.env('CLIENT_SECRET'), - }, - form: true, - }).then(({ body }: any) => { - const user: any = jwt.decode(body.id_token) - const userItem = { - token: body.access_token, - user: { - ...user, + return cy + .request({ + method: 'POST', + url: Cypress.env('OIDC_ISSUER') + '/protocol/openid-connect/token', + body: { + grant_type: 'password', + username: Cypress.env('DEV_USERNAME'), + password: Cypress.env('DEV_PASSWORD'), + Scope: 'openid', + client_id: Cypress.env('CLIENT_ID'), + client_secret: Cypress.env('CLIENT_SECRET'), }, - } - cy.log(JSON.stringify(userItem)) - }) - log.snapshot('after') - log.end() + form: true, + }) + .then(({ body }: any) => { + const user: any = jwt.decode(body.id_token) + const userItem = { + token: body.access_token, + user: { + ...user, + }, + } + log.snapshot('after') + log.end() + return userItem + }) }) Cypress.Commands.add('logout', () => { - cy.log('< Logging out') cy.getSession().then(() => { cy.get('@session').then((res: any) => { @@ -209,7 +319,6 @@ Cypress.Commands.add('logout', () => { }) Cypress.Commands.add('keycloakLogout', () => { - cy.log('< Logging out') cy.get('.dropdown-toggle.ng-binding').click() cy.contains('Sign Out').click() @@ -228,7 +337,7 @@ Cypress.Commands.add('getAccessToken', (client_id: string, client_secret: string client_secret, }, form: true, - failOnStatusCode: false + failOnStatusCode: false, }).then((res) => { cy.wrap(res).as('accessTokenResponse') // expect(res.status).to.eq(200) @@ -244,47 +353,62 @@ Cypress.Commands.add('getServiceOrRouteID', (configType: string, host: string) = }).then((res) => { expect(res.status).to.eq(200) if (config === 'routes') { - cy.saveState(config + 'ID', Cypress._.get((Cypress._.filter(res.body.data, ["hosts", [host + ".api.gov.bc.ca"]]))[0], 'id')) - } - else { - cy.saveState(config + 'ID', Cypress._.get((Cypress._.filter(res.body.data, ["name", host]))[0], 'id')) + cy.saveState( + config + 'ID', + Cypress._.get( + Cypress._.filter(res.body.data, ['hosts', [host + '.api.gov.bc.ca']])[0], + 'id' + ) + ) + } else { + cy.saveState( + config + 'ID', + Cypress._.get(Cypress._.filter(res.body.data, ['name', host])[0], 'id') + ) } }) }) -Cypress.Commands.add('publishApi', (fileNames: any, namespace: string, flag?: boolean) => { - let fixtureFile = flag ? "state/regen" : "state/store"; - cy.log('< Publish API') - let fileName = '' - if (fileNames instanceof Array) { - for (const filepath of fileNames) { - fileName = fileName + ' ./cypress/fixtures/' + filepath; +Cypress.Commands.add( + 'publishApi', + (fileNames: any, namespace: string, flag?: boolean) => { + let fixtureFile = flag ? 'state/regen' : 'state/store' + cy.log('< Publish API') + let fileName = '' + if (fileNames instanceof Array) { + for (const filepath of fileNames) { + fileName = fileName + ' ./cypress/fixtures/' + filepath + } + } else { + fileName = ' ./cypress/fixtures/' + fileNames } - } - else { - fileName = ' ./cypress/fixtures/' + fileNames - } - const requestName: string = 'publishAPI' - cy.fixture(fixtureFile).then((creds: any) => { - const serviceAcctCreds = JSON.parse(creds.credentials) - cy.getAccessToken(serviceAcctCreds.clientId, serviceAcctCreds.clientSecret).then( - () => { - cy.wait(3000) - cy.get('@accessTokenResponse').then((res: any) => { - cy.executeCliCommand('gwa config set --namespace ' + namespace).then((response) => { - cy.executeCliCommand('gwa config set --token ' + res.body.access_token).then((response) => { - { - expect(response.stdout).to.contain("Config settings saved") - cy.executeCliCommand('gwa pg ' + fileName).then((response) => { - expect(response.stdout).to.contain("Gateway config published") + const requestName: string = 'publishAPI' + cy.fixture(fixtureFile).then((creds: any) => { + const serviceAcctCreds = JSON.parse(creds.credentials) + cy.getAccessToken(serviceAcctCreds.clientId, serviceAcctCreds.clientSecret).then( + () => { + cy.wait(3000) + cy.get('@accessTokenResponse').then((res: any) => { + cy.executeCliCommand('gwa config set --gateway ' + namespace).then( + (response) => { + cy.executeCliCommand( + 'gwa config set --token ' + res.body.access_token + ).then((response) => { + { + expect(response.stdout).to.contain('Config settings saved') + cy.executeCliCommand('gwa pg ' + fileName).then((response) => { + expect(response.stdout).to.contain('Gateway config published') + }) + } }) } - }) + ) }) - }) - }) - }) -}) + } + ) + }) + } +) Cypress.Commands.add('deleteAllCookies', () => { cy.clearCookies() @@ -293,120 +417,133 @@ Cypress.Commands.add('deleteAllCookies', () => { cy.clearCookie('keystone.sid') cy.clearCookie('_oauth2_proxy') cy.exec('npm cache clear --force') - var cookies = document.cookie.split(";"); + var cookies = document.cookie.split(';') for (var i = 0; i < cookies.length; i++) { - var cookie = cookies[i]; - var eqPos = cookie.indexOf("="); - var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; - document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; + var cookie = cookies[i] + var eqPos = cookie.indexOf('=') + var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie + document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT' } }) -Cypress.Commands.add('makeKongRequest', (serviceName: string, methodType: string, key?: string) => { - let authorization - cy.fixture('state/regen').then((creds: any) => { - cy.wait(5000) - let token = key - if (key == undefined) { - token = creds.apikey +Cypress.Commands.add( + 'makeKongRequest', + (serviceName: string, methodType: string, key?: string) => { + let authorization + cy.fixture('state/regen').then((creds: any) => { + cy.wait(5000) + let token = key + if (key == undefined) { + token = creds.apikey + } + const service = serviceName + cy.log('Token->' + token) + return cy.request({ + url: Cypress.env('KONG_URL'), + method: methodType, + headers: { 'x-api-key': `${token}`, Host: `${service}` + '.api.gov.bc.ca' }, + failOnStatusCode: false, + auth: { + bearer: token, + }, + }) + }) + } +) + +Cypress.Commands.add( + 'makeKongGatewayRequest', + (endpoint: string, requestName: string, methodType: string) => { + let body = {} + var serviceEndPoint = endpoint + body = config[requestName] + if (requestName == '') { + body = {} } - const service = serviceName - cy.log("Token->" + token) return cy.request({ - url: Cypress.env('KONG_URL'), + url: Cypress.env('KONG_CONFIG_URL') + '/' + serviceEndPoint, method: methodType, - headers: { 'x-api-key': `${token}`, 'Host': `${service}` + '.api.gov.bc.ca' }, + body: body, + form: true, failOnStatusCode: false, - auth: { - bearer: token, - } }) - }) -}) - -Cypress.Commands.add('makeKongGatewayRequest', (endpoint: string, requestName: string, methodType: string) => { - let body = {} - var serviceEndPoint = endpoint - body = config[requestName] - if (requestName == '') { - body = {} } - return cy.request({ - url: Cypress.env('KONG_CONFIG_URL') + '/' + serviceEndPoint, - method: methodType, - body: body, - form: true, - failOnStatusCode: false - }) -}) - -Cypress.Commands.add('makeKongGatewayRequestUsingClientIDSecret', (hostURL: string, methodType = 'GET') => { - cy.readFile('cypress/fixtures/state/store.json').then((store_res) => { - - let cc = JSON.parse(store_res.clientidsecret) - - cy.getAccessToken(cc.clientId, cc.clientSecret).then(() => { - cy.get('@accessTokenResponse').then((token_res: any) => { - let token = token_res.body.access_token - cy.request({ - method: methodType, - url: Cypress.env('KONG_URL'), - failOnStatusCode: false, - headers: { - Host: hostURL, - }, - auth: { - bearer: token, - }, +) + +Cypress.Commands.add( + 'makeKongGatewayRequestUsingClientIDSecret', + (hostURL: string, methodType = 'GET') => { + cy.readFile('cypress/fixtures/state/store.json').then((store_res) => { + let cc = JSON.parse(store_res.clientidsecret) + + cy.getAccessToken(cc.clientId, cc.clientSecret).then(() => { + cy.get('@accessTokenResponse').then((token_res: any) => { + let token = token_res.body.access_token + cy.request({ + method: methodType, + url: Cypress.env('KONG_URL'), + failOnStatusCode: false, + headers: { + Host: hostURL, + }, + auth: { + bearer: token, + }, + }) }) }) }) - }) -}) - -Cypress.Commands.add('updateKongPlugin', (pluginName: string, name: string, endPoint?: string, verb = 'POST') => { - cy.fixture('state/store').then((creds: any) => { - let body = {} - const pluginID = pluginName.toLowerCase() + 'id' - const id = creds[pluginID] - let endpoint - if (pluginName == '') - endpoint = 'plugins' - else if (id !== undefined) - endpoint = pluginName.toLowerCase() + '/' + id.toString() + '/' + 'plugins' - endpoint = (typeof endPoint !== 'undefined') ? endPoint : endpoint - body = config[name] - cy.log("Body->" + body) - return cy.request({ - url: Cypress.env('KONG_CONFIG_URL') + '/' + endpoint, - method: verb, - body: body, - form: true, - failOnStatusCode: false + } +) + +Cypress.Commands.add( + 'updateKongPlugin', + (pluginName: string, name: string, endPoint?: string, verb = 'POST') => { + cy.fixture('state/store').then((creds: any) => { + let body = {} + const pluginID = pluginName.toLowerCase() + 'id' + const id = creds[pluginID] + let endpoint + if (pluginName == '') endpoint = 'plugins' + else if (id !== undefined) + endpoint = pluginName.toLowerCase() + '/' + id.toString() + '/' + 'plugins' + endpoint = typeof endPoint !== 'undefined' ? endPoint : endpoint + body = config[name] + cy.log('Body->' + body) + return cy.request({ + url: Cypress.env('KONG_CONFIG_URL') + '/' + endpoint, + method: verb, + body: body, + form: true, + failOnStatusCode: false, + }) }) - }) -}) - -Cypress.Commands.add('updateKongPluginForJSONRequest', (jsonBody: string, endPoint: string, verb = 'POST') => { - cy.fixture('state/store').then((creds: any) => { - let body = {} - let headers = { "content-type": "application/json", "accept": "application/json" } - body = jsonBody - return cy.request({ - url: Cypress.env('KONG_CONFIG_URL') + '/' + endPoint, - method: verb, - body: body, - headers: headers, - failOnStatusCode: false + } +) + +Cypress.Commands.add( + 'updateKongPluginForJSONRequest', + (jsonBody: string, endPoint: string, verb = 'POST') => { + cy.fixture('state/store').then((creds: any) => { + let body = {} + let headers = { 'content-type': 'application/json', accept: 'application/json' } + body = jsonBody + return cy.request({ + url: Cypress.env('KONG_CONFIG_URL') + '/' + endPoint, + method: verb, + body: body, + headers: headers, + failOnStatusCode: false, + }) }) - }) -}) + } +) -Cypress.Commands.add("generateKeystore", async () => { +Cypress.Commands.add('generateKeystore', async () => { let keyStore = jose.JWK.createKeyStore() await keyStore.generate('RSA', 2048, { alg: 'RS256', use: 'sig' }) return JSON.stringify(keyStore.toJSON(true), null, ' ') -}); +}) Cypress.Commands.add('setHeaders', (headerValues: any) => { headers = headerValues @@ -417,60 +554,135 @@ Cypress.Commands.add('setRequestBody', (body: any) => { }) Cypress.Commands.add('setAuthorizationToken', (token: string) => { - headers["Authorization"] = "Bearer " + token - headers = headers + headers['Authorization'] = 'Bearer ' + token +}) + +Cypress.Commands.add('callAPI', (endPoint: string, methodType: string) => { + let body = '{}' + let requestData: any = {} + if (methodType.toUpperCase() === 'PUT' || methodType.toUpperCase() === 'POST') { + body = requestBody + } + + requestData['appname'] = 'Test1' + requestData['url'] = Cypress.env('BASE_URL') + '/' + endPoint + requestData['headers'] = headers + requestData['body'] = '' + requestData['method'] = methodType + + cy.request({ + url: Cypress.env('BASE_URL') + '/' + endPoint, + method: methodType, + body, + headers, + failOnStatusCode: false, + }).then((apiResponse) => { + // You can also return data or use it in further tests + const responseData = { + apiRes: apiResponse, + } + // cy.addToAstraScanIdList(response2.body.status) + return responseData + }) }) Cypress.Commands.add('makeAPIRequest', (endPoint: string, methodType: string) => { - let body = {} let requestData: any = {} if (methodType.toUpperCase() === 'PUT' || methodType.toUpperCase() === 'POST') { body = requestBody } - requestData['appname'] = "Test1" + requestData['appname'] = 'Test1' requestData['url'] = Cypress.env('BASE_URL') + '/' + endPoint requestData['headers'] = headers - requestData['body'] = "" + requestData['body'] = '' requestData['method'] = methodType - + // Scan request with Astra - cy.request({ - url: 'http://astra.localtest.me:8094/scan/', - method: 'POST', - body: requestData, - headers: headers, - failOnStatusCode: false - }).then((astraResponse) => { - // Actual API request + if (Cypress.env('ASTRA_SCAN_ENABLED') == 'true') { + cy.request({ + url: 'http://astra.localtest.me:8094/scan/', + method: 'POST', + body: requestData, + headers: headers, + failOnStatusCode: false, + }).then((astraResponse) => { + // Actual API request + cy.request({ + url: Cypress.env('BASE_URL') + '/' + endPoint, + method: methodType, + body: body, + headers: headers, + failOnStatusCode: false, + }).then((apiResponse) => { + // You can also return data or use it in further tests + const responseData = { + astraRes: astraResponse, + apiRes: apiResponse, + } + // cy.addToAstraScanIdList(response2.body.status) + return responseData + }) + }) + } else { cy.request({ url: Cypress.env('BASE_URL') + '/' + endPoint, method: methodType, body: body, headers: headers, - failOnStatusCode: false + failOnStatusCode: false, + }).then((apiResponse) => { + const responseData = { + astraRes: { body: { status: null } }, + apiRes: apiResponse, + } + return responseData + }) + } +}) + +Cypress.Commands.add('gqlQuery', (query, variables = {}) => { + cy.loginByAuthAPI('', '').then((token_res: any) => { + cy.setHeaders({ + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }) + cy.setAuthorizationToken(token_res.token) + return cy.request({ + url: Cypress.env('BASE_URL') + '/gql/api', + method: 'POST', + headers, + body: JSON.stringify({ query, variables }), + failOnStatusCode: false, }).then((apiResponse) => { // You can also return data or use it in further tests const responseData = { - astraRes: astraResponse, apiRes: apiResponse, - }; + } // cy.addToAstraScanIdList(response2.body.status) - return responseData; + return responseData }) - }); + }) }) Cypress.Commands.add('makeAPIRequestForScanResult', (scanID: string) => { return cy.request({ - url: 'http://astra.localtest.me:8094/alerts/' + scanID, + url: 'http://astra.localtest.me:8094/alerts/' + scanID, method: 'GET', - failOnStatusCode: false + failOnStatusCode: false, }) }) Cypress.Commands.add('getUserSession', () => { + cy.request({ + method: 'GET', + url: Cypress.config('baseUrl') + '/admin/session', + failOnStatusCode: true + }).as('login') +}); + +Cypress.Commands.add('interceptUserSession', () => { cy.intercept(Cypress.config('baseUrl') + '/admin/session').as('login') }) @@ -478,151 +690,168 @@ Cypress.Commands.add('selectLoginOptions', (username: string) => { cy.get(login.loginDropDown).click() if (username.includes('idir')) { login.selectAPIProviderLoginOption() - } - else { + } else { login.selectDeveloperLoginOption() } }) Cypress.Commands.add('verifyToastMessage', (msg: string) => { - cy.get('[role="alert"]', { timeout: 2000 }).closest('div').invoke('text') + cy.get('[role="alert"]', { timeout: 2000 }) + .closest('div') + .invoke('text') .then((text) => { - const toastText = text; - expect(toastText).to.contain(msg); + const toastText = text + expect(toastText).to.contain(msg) }) }) -Cypress.Commands.add('compareJSONObjects', (actualResponse: any, expectedResponse: any, indexFlag = false) => { - let response = actualResponse - if (indexFlag) { - const index = actualResponse.findIndex((x: { name: string }) => x.name === expectedResponse.name); - response = actualResponse[index] - } - for (var p in expectedResponse) { - if (typeof (expectedResponse[p]) === "object") { - var objectValue1 = expectedResponse[p], - objectValue2 = response[p]; - for (var value in objectValue1) { - if (!(['activityAt', 'id'].includes(value))) { - cy.compareJSONObjects(objectValue2[value], objectValue1[value]); +Cypress.Commands.add( + 'compareJSONObjects', + (actualResponse: any, expectedResponse: any, indexFlag = false) => { + let response = actualResponse + if (indexFlag) { + const index = actualResponse.findIndex( + (x: { name: string }) => x.name === expectedResponse.name + ) + response = actualResponse[index] + } + for (var p in expectedResponse) { + if (typeof expectedResponse[p] === 'object') { + var objectValue1 = expectedResponse[p], + objectValue2 = response[p] + for (var value in objectValue1) { + if (!['activityAt', 'id'].includes(value)) { + cy.compareJSONObjects(objectValue2[value], objectValue1[value]) + } + } + } else { + if (expectedResponse[p] == 'true' || expectedResponse[p] == 'false') + Boolean(expectedResponse[p]) + if (['organization', 'organizationUnit'].includes(p) && !indexFlag) { + response[p] = response[p]['name'] + } + if ( + response[p] !== expectedResponse[p] && + !['clientSecret', 'appId', 'isInCatalog', 'isDraft', 'consumer', 'id'].includes( + p + ) + ) { + cy.log('Different Value ->' + expectedResponse[p]) + assert.fail('JSON value mismatch for ' + p) } - } - } else { - if ((expectedResponse[p] == 'true') || (expectedResponse[p] == 'false')) - Boolean(expectedResponse[p]) - if (['organization', 'organizationUnit'].includes(p) && (!indexFlag)) { - response[p] = response[p]['name'] - } - if ((response[p] !== expectedResponse[p]) && !(['clientSecret', 'appId', 'isInCatalog', 'isDraft', 'consumer', 'id'].includes(p))) { - cy.log("Different Value ->" + expectedResponse[p]) - assert.fail("JSON value mismatch for " + p) } } } -}) +) + +Cypress.Commands.add( + 'updatePluginFile', + (filename: string, serviceName: string, pluginFileName: string) => { + cy.readFile('cypress/fixtures/' + pluginFileName).then(($el) => { + let newObj: any + newObj = YAML.parse($el) + cy.readFile('cypress/fixtures/' + filename).then((content: any) => { + let obj = YAML.parse(content) + const keys = Object.keys(obj) + Object.keys(obj.services).forEach(function (key, index) { + if (obj.services[index].name == serviceName) { + obj.services[index].plugins = newObj.plugins + } + }) + const yamlString = YAML.stringify(obj, 'utf8') + cy.writeFile('cypress/fixtures/' + filename, yamlString) + }) + }) + } +) -Cypress.Commands.add('updatePluginFile', (filename: string, serviceName: string, pluginFileName: string) => { - cy.readFile('cypress/fixtures/' + pluginFileName).then(($el) => { - let newObj: any - newObj = YAML.parse($el) +Cypress.Commands.add( + 'updatePropertiesOfPluginFile', + (filename: string, propertyName: any, propertyValue: any) => { cy.readFile('cypress/fixtures/' + filename).then((content: any) => { let obj = YAML.parse(content) - const keys = Object.keys(obj); - Object.keys(obj.services).forEach(function (key, index) { - if (obj.services[index].name == serviceName) { - obj.services[index].plugins = newObj.plugins - } - }); - const yamlString = YAML.stringify(obj, 'utf8'); + const keys = Object.keys(obj) + if (propertyName === 'config.anonymous') { + obj.plugins[0].config.anonymous = propertyValue + } else if (propertyName === 'tags') { + obj.plugins[0][propertyName] = propertyValue + } else { + Object.keys(obj.services).forEach(function (key, index) { + if (propertyName == 'methods') { + obj.services[0].routes[0].methods = propertyValue + } else { + obj.services[0].plugins[0].config[propertyName] = propertyValue + } + }) + } + const yamlString = YAML.stringify(obj, 'utf8') cy.writeFile('cypress/fixtures/' + filename, yamlString) }) - }) -}) + } +) +Cypress.Commands.add( + 'getTokenUsingJWKCredentials', + (credential: any, privateKey: any) => { + let jwkCred = JSON.parse(credential) + let clientId = jwkCred.clientId + let tokenEndpoint = jwkCred.tokenEndpoint + let now = Math.floor(new Date().getTime() / 1000) + let plus5Minutes = new Date((now + 5 * 60) * 1000) + let alg = 'RS256' -Cypress.Commands.add('updatePropertiesOfPluginFile', (filename: string, propertyName: any, propertyValue: any) => { - cy.readFile('cypress/fixtures/' + filename).then((content: any) => { - let obj = YAML.parse(content) - const keys = Object.keys(obj); - if (propertyName === "config.anonymous") { - obj.plugins[0].config.anonymous = propertyValue - } - else if (propertyName === "tags") { - obj.plugins[0][propertyName] = propertyValue - } - else { - Object.keys(obj.services).forEach(function (key, index) { - if (propertyName == "methods") { - obj.services[0].routes[0].methods = propertyValue - } - else { - obj.services[0].plugins[0].config[propertyName] = propertyValue - } - }); + let claims = { + aud: Cypress.env('OIDC_ISSUER'), } - const yamlString = YAML.stringify(obj, 'utf8'); - cy.writeFile('cypress/fixtures/' + filename, yamlString) - }) -}) -Cypress.Commands.add('getTokenUsingJWKCredentials', (credential: any, privateKey: any) => { - let jwkCred = JSON.parse(credential) - let clientId = jwkCred.clientId - let tokenEndpoint = jwkCred.tokenEndpoint + let jwt = njwt + .create(claims, privateKey, alg) + .setIssuedAt(now) + .setExpiration(plus5Minutes) + .setIssuer(clientId) + .setSubject(clientId) + .compact() - let now = Math.floor(new Date().getTime() / 1000) - let plus5Minutes = new Date((now + 5 * 60) * 1000) - let alg = 'RS256' - - let claims = { - aud: Cypress.env('OIDC_ISSUER') + '/auth/realms/master', + cy.request({ + url: tokenEndpoint, + method: 'POST', + body: { + grant_type: 'client_credentials', + client_id: clientId, + scopes: 'openid', + client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', + client_assertion: jwt, + }, + form: true, + }) } +) - let jwt = njwt - .create(claims, privateKey, alg) - .setIssuedAt(now) - .setExpiration(plus5Minutes) - .setIssuer(clientId) - .setSubject(clientId) - .compact() - - cy.request({ - url: tokenEndpoint, - method: 'POST', - body: { - grant_type: 'client_credentials', - client_id: clientId, - scopes: 'openid', - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - client_assertion: jwt, - }, - form: true, - }) -}) - -Cypress.Commands.add("generateKeyPair", () => { - const keypair = forge.pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 }); +Cypress.Commands.add('generateKeyPair', () => { + const keypair = forge.pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 }) // Convert the key pair to PEM format - const privateKeyPem = forge.pki.privateKeyToPem(keypair.privateKey); - const publicKeyPem = forge.pki.publicKeyToPem(keypair.publicKey); + const privateKeyPem = forge.pki.privateKeyToPem(keypair.privateKey) + const publicKeyPem = forge.pki.publicKeyToPem(keypair.publicKey) cy.writeFile('cypress/fixtures/state/jwtReGenPrivateKey_new.pem', privateKeyPem) cy.writeFile('cypress/fixtures/state/jwtReGenPublicKey_new.pub', publicKeyPem) - }) Cypress.Commands.add('forceVisit', (url: string) => { - cy.window().then(win => { - return win.open(url, '_self'); - }); -}); + cy.window().then((win) => { + return win.open(url, '_self') + }) +}) -Cypress.Commands.add('updateJsonBoby', (json: any, key: string, newValue: string): any => { - json[key] = newValue - return json -}); +Cypress.Commands.add( + 'updateJsonBoby', + (json: any, key: string, newValue: string): any => { + json[key] = newValue + return json + } +) const formDataRequest = ( options: formDataRequestOptions, diff --git a/e2e/cypress/support/e2e.ts b/e2e/cypress/support/e2e.ts index 997e3ea59..57598e6a0 100644 --- a/e2e/cypress/support/e2e.ts +++ b/e2e/cypress/support/e2e.ts @@ -1,6 +1,7 @@ import './commands' import 'cypress-xpath' import './auth-commands' +import './prep-commands' import './util-commands' import '@cypress/code-coverage/support' const _ = require('lodash') diff --git a/e2e/cypress/support/global.d.ts b/e2e/cypress/support/global.d.ts index 66c452b27..433e29b3f 100644 --- a/e2e/cypress/support/global.d.ts +++ b/e2e/cypress/support/global.d.ts @@ -2,6 +2,11 @@ /// declare namespace Cypress { + interface Namespace { + id: string; + name: string; + } + interface Chainable { login(username: string, password: string, skipFlag?: boolean): Chainable @@ -21,11 +26,33 @@ declare namespace Cypress { makeKongRequest(serviceName: string, methodType: string, key?: string): Chainable - makeKongGatewayRequestUsingClientIDSecret(hostURL: string, methodType?: string): Chainable + makeKongGatewayRequestUsingClientIDSecret( + hostURL: string, + methodType?: string + ): Chainable preserveCookiesDefaults(): void - saveState(key: string, value: string, flag?: boolean, isGlobal?: boolean): Chainable + createGateway( + gatewayid?: string, + displayname?: string, + ): Chainable + + deleteGatewayCli(gatewayid: string, force: boolean): Chainable + + activateGateway( + gatewayId: string, + checkNoNamespace?: boolean + ): Chainable> + + getGateways(): Chainable + + saveState( + key: string, + value: string, + flag?: boolean, + isGlobal?: boolean + ): Chainable getState(key: string): Chainable @@ -36,14 +63,29 @@ declare namespace Cypress { client_secret: string ): Chainable> - publishApi(fileNames: any, namespace: string, flag?: boolean): Chainable> + publishApi( + fileNames: any, + namespace: string, + flag?: boolean + ): Chainable> - getServiceOrRouteID(configType: string, host: string + getServiceOrRouteID( + configType: string, + host: string ): Chainable> - updateKongPlugin(pluginName: string, name: string, endPoint?: string, verb?: string): Chainable> + updateKongPlugin( + pluginName: string, + name: string, + endPoint?: string, + verb?: string + ): Chainable> - makeKongGatewayRequest(endpoint: string, requestName: string, methodType: string): Chainable> + makeKongGatewayRequest( + endpoint: string, + requestName: string, + methodType: string + ): Chainable> // generateKeystore() : Chainable @@ -54,26 +96,54 @@ declare namespace Cypress { setRequestBody(requestBody: any): void setAuthorizationToken(token: string): void + + gqlQuery(query: string, variables?: Record): Chainable + + callAPI(endPoint: string, methodType: string): Chainable> makeAPIRequest(endPoint: string, methodType: string): Chainable> getUserSession(): Chainable> + + interceptUserSession(): Chainable> - compareJSONObjects(actualResponse: any, expectedResponse: any, indexFlag?: boolean): Chainable> + compareJSONObjects( + actualResponse: any, + expectedResponse: any, + indexFlag?: boolean + ): Chainable> - getUserSessionTokenValue(namespace: string, isNamespaceSelected?: boolean): Chainable> + getUserSessionTokenValue( + namespace: string, + isNamespaceSelected?: boolean + ): Chainable> getUserSessionResponse(): Chainable> - getTokenUsingJWKCredentials(credential: any, privateKey: any): Chainable> + getTokenUsingJWKCredentials( + credential: any, + privateKey: any + ): Chainable> verifyToastMessage(msg: string): Chainable> - updatePluginFile(filename: string, serviceName: string, pluginFileName: string): Chainable> + updatePluginFile( + filename: string, + serviceName: string, + pluginFileName: string + ): Chainable> - updateElementsInPluginFile(filename: string, elementName: string, elementValue: string): Chainable> + updateElementsInPluginFile( + filename: string, + elementName: string, + elementValue: string + ): Chainable> - updatePropertiesOfPluginFile(filename: string, propertyName: any, propertyValue: any): Chainable> + updatePropertiesOfPluginFile( + filename: string, + propertyName: any, + propertyValue: any + ): Chainable> keycloakLogin(username: string, password: string): Chainable @@ -84,28 +154,47 @@ declare namespace Cypress { generateKeyPair(): void // isProductDisplay(productName: string, expResult : boolean) :Chainable> - updateJsonValue(filePath: string, jsonPath: string, newValue: string, index?: any): Chainable - - updateKongPluginForJSONRequest(jsonBody: string, endPoint: string, verb?: string): Chainable> + updateJsonValue( + filePath: string, + jsonPath: string, + newValue: string, + index?: any + ): Chainable + + updateKongPluginForJSONRequest( + jsonBody: string, + endPoint: string, + verb?: string + ): Chainable> forceVisit(url: string): Chainable executeCliCommand(command: string): Chainable - replaceWordInJsonObject(targetWord: string, replacement: string, fileName: string): Chainable> + replaceWordInJsonObject( + targetWord: string, + replacement: string, + fileName: string + ): Chainable> gwaPublish(type: string, fileName: string): Chainable> - replaceWord(originalString: string, wordToReplace: string, replacementWord: string): Chainable + replaceWord( + originalString: string, + wordToReplace: string, + replacementWord: string + ): Chainable - updateJsonBoby(json: any, key: string, newValue: string):Chainable + updateJsonBoby(json: any, key: string, newValue: string): Chainable - deleteFileInE2EFolder(fileName: string):Chainable + deleteFileInE2EFolder(fileName: string): Chainable - addToAstraScanIdList(item: any):Chainable - - checkAstraScanResultForVulnerability():Chainable + addToAstraScanIdList(item: any): Chainable + + checkAstraScanResultForVulnerability(): Chainable makeAPIRequestForScanResult(scanID: string): Chainable> + + buildOrgGatewayDatasetAndProduct(): Chainable> } } diff --git a/e2e/cypress/support/prep-commands.ts b/e2e/cypress/support/prep-commands.ts new file mode 100644 index 000000000..077c837d6 --- /dev/null +++ b/e2e/cypress/support/prep-commands.ts @@ -0,0 +1,206 @@ +const { v4: uuidv4 } = require('uuid') + +Cypress.Commands.add('buildOrgGatewayDatasetAndProduct', (): Cypress.Chainable => { + const datasetId = uuidv4().replace(/-/g, '').toUpperCase().substring(0, 4) + + return cy.loginByAuthAPI('', '').then((token_res: any) => { + cy.setHeaders({ 'Content-Type': 'application/json' }) + cy.setAuthorizationToken(token_res.token) + + // just reference the dataset id for easier tracing + const orgId = datasetId + + const org = { + name: `ministry-of-kittens-${orgId}`, + title: 'Some good title about kittens', + description: 'Some good description about kittens', + tags: [], + orgUnits: [ + { + name: `division-of-toys-${orgId}`, + title: 'Division of fun toys to play', + description: 'Some good description about how we manage our toys', + tags: [], + extForeignKey: `division-of-toys-${orgId}`, + extSource: 'internal', + extRecordHash: '', + }, + ], + extSource: 'internal', + extRecordHash: '', + } + + // New organization and org unit + cy.setRequestBody(org) + return cy + .callAPI('ds/api/v3/organizations/ca.bc.gov', 'PUT') + .then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + const orgAccess = { + name: org.name, + parent: `/ca.bc.gov`, + members: [ + { + member: { + email: 'janis@testmail.com', + }, + roles: ['organization-admin'], + }, + ], + } + cy.setRequestBody(orgAccess) + + // Set permissions for the new Org + cy.callAPI(`ds/api/v3/organizations/ca.bc.gov/access`, 'PUT').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(204) + } + ) + + const orgUnitAccess = { + name: org.orgUnits[0].name, + parent: `/ca.bc.gov/${org.name}`, + members: [ + { + member: { + email: 'janis@testmail.com', + }, + roles: ['organization-admin'], + }, + ], + } + cy.setRequestBody(orgUnitAccess) + + // Set permissions for the new Org Unit + cy.callAPI(`ds/api/v3/organizations/ca.bc.gov/access`, 'PUT').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(204) + } + ) + + const payload = { + name: `org-dataset-${datasetId}`, + license_title: 'Open Government Licence - British Columbia', + security_class: 'PUBLIC', + view_audience: 'Public', + download_audience: 'Public', + record_publish_date: '2017-09-05', + notes: 'Some notes', + title: 'A title about my dataset', + tags: ['tag1', 'tag2'], + organization: org.name, + organizationUnit: org.orgUnits[0].name, + } + cy.setRequestBody(payload) + + // New dataset + return cy + .callAPI(`ds/api/v3/organizations/${org.name}/datasets`, 'PUT') + .then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + const match = { status: 200, result: 'created', childResults: [] } + const datasetId = body.id + delete body.id + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + + return cy + .callAPI( + `ds/api/v3/organizations/${org.name}/datasets/${payload.name}`, + 'GET' + ) + .then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + const match = { + name: payload.name, + license_title: 'Open Government Licence - British Columbia', + security_class: 'PUBLIC', + view_audience: 'Public', + download_audience: 'Public', + record_publish_date: '2017-09-05', + notes: 'Some notes', + title: 'A title about my dataset', + isInCatalog: false, + isDraft: true, + contacts: [], + resources: [], + tags: ['tag1', 'tag2'], + organization: { + name: org.name, + title: 'Some good title about kittens', + tags: [], + description: 'Some good description about kittens', + }, + organizationUnit: { + name: org.orgUnits[0].name, + title: 'Division of fun toys to play', + tags: [], + description: 'Some good description about how we manage our toys', + }, + } + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + + // New Gateway + cy.setRequestBody({}) + return cy + .callAPI('ds/api/v3/gateways', 'POST') + .then(({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + const myGateway = body + + // Assign gateway to Org + cy.callAPI( + `ds/api/v3/organizations/${org.name}/${org.orgUnits[0].name}/gateways/${myGateway.gatewayId}?enable=true`, + 'PUT' + ).then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + expect(body.result).to.be.equal('namespace-assigned') + }) + + const product = { + name: `my-product-on-${myGateway.gatewayId}`, + dataset: payload.name, + environments: [ + { + name: 'dev', + active: true, + approval: false, + flow: 'public', + }, + ], + } + cy.setRequestBody(product) + + // New Product and Active Environment + return cy + .callAPI( + `ds/api/v3/gateways/${myGateway.gatewayId}/products`, + 'PUT' + ) + .then(({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + + const match = { + status: 200, + result: 'created', + childResults: [], + } + delete body.id + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + + return { + org, + gateway: myGateway, + dataset: payload, + datasetId, + product, + } + }) + }) + }) + }) + }) + }) +}) diff --git a/e2e/cypress/support/util-commands.ts b/e2e/cypress/support/util-commands.ts index c42c1a009..c8d27ffba 100644 --- a/e2e/cypress/support/util-commands.ts +++ b/e2e/cypress/support/util-commands.ts @@ -171,19 +171,23 @@ Cypress.Commands.add('deleteFileInE2EFolder', (fileName: string) => { }); Cypress.Commands.add('addToAstraScanIdList', (item) => { - cy.readFile('cypress/fixtures/state/scanID.json').then((fileContent) => { - // Initialize the list if it doesn't exist - const items = fileContent.items || []; + if (Cypress.env('ASTRA_SCAN_ENABLED') == 'true') { + cy.readFile('cypress/fixtures/state/scanID.json').then((fileContent) => { + // Initialize the list if it doesn't exist + const items = fileContent.items || []; - // Append the new item to the list - items.push(item); + // Append the new item to the list + items.push(item); - // Create an object with the updated list - const updatedData = { items }; + // Create an object with the updated list + const updatedData = { items }; - // Write the updated object back to the file - cy.writeFile('cypress/fixtures/state/scanID.json', updatedData); - }); + // Write the updated object back to the file + cy.writeFile('cypress/fixtures/state/scanID.json', updatedData); + }); + } else { + cy.log('Astra Scan is disabled') + } }); Cypress.Commands.add('replaceWord', (originalString: string, wordToReplace: string, replacementWord: string)=> { diff --git a/e2e/cypress/tests/01-api-key/00-get-started.cy.ts b/e2e/cypress/tests/01-api-key/00-get-started.cy.ts new file mode 100644 index 000000000..60d30074e --- /dev/null +++ b/e2e/cypress/tests/01-api-key/00-get-started.cy.ts @@ -0,0 +1,74 @@ +import LoginPage from '../../pageObjects/login' +import NameSpacePage from '../../pageObjects/namespace' +const cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); + +// Elements of this page must be checked when there are no Gateways, so this test comes first of all +describe('Gateway Get Started page', () => { + const login = new LoginPage() + const ns = new NameSpacePage() + let userSession: any + let namespace: string + + before(() => { + cy.visit('/') + cy.deleteAllCookies() + cy.reload(true) + cy.resetState() + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + cy.fixture('common-testdata').as('common-testdata') + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.getUserSessionTokenValue('', false).then((value) => { + userSession = value + }) + }) + + it('Set token with gwa config command', () => { + cy.exec('gwa config set --token ' + userSession, { timeout: 3000, failOnNonZeroExit: false }).then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('Set environment with gwa config command', () => { + cy.executeCliCommand('gwa config set --host ' + cleanedUrl + ' --scheme http').then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('Check for redirect to Get Started page', () => { + cy.visit(ns.path) + cy.url().should('include', '/manager/gateways/get-started') + }) + + it('Check for box that says "No gateways"', () => { + cy.get('[data-testid="no-gateways"]').should('exist') + }) + + it('Create a namespace', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + }) + }) + + it('Check for banner that says "You have gateways"', () => { + cy.reload() + cy.get('[data-testid="no-gateways"]').should('not.exist') + cy.get('[data-testid="you-have-gateways-banner"]').should('exist') + }) + + it('Cleanup: delete namespace', () => { + cy.deleteGatewayCli(namespace, true) + }) + + after(() => { + cy.logout() + cy.clearLocalStorage({ log: true }) + cy.deleteAllCookies() + }) + +}) \ No newline at end of file diff --git a/e2e/cypress/tests/01-api-key/01-create-api.cy.ts b/e2e/cypress/tests/01-api-key/01-create-api.cy.ts index 4817e4cfd..c6c8dfde8 100644 --- a/e2e/cypress/tests/01-api-key/01-create-api.cy.ts +++ b/e2e/cypress/tests/01-api-key/01-create-api.cy.ts @@ -52,20 +52,19 @@ describe('Create API Spec', () => { cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ namespace }: any) => { nameSpace = namespace - home.createNamespace(namespace) + cy.createGateway(namespace) + cy.activateGateway(namespace) + cy.visit('/manager/gateways/detail') + cy.get('[data-testid="ns-detail-gatewayid"]').then(($el) => { + expect($el).contain(namespace) + }) cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] + userSession = xhr.headers['x-auth-request-access-token'] }) }) }) }) - it('Verify for invalid namespace name', () => { - cy.get('@apiowner').then(({ invalid_namespace }: any) => { - home.validateNamespaceName(invalid_namespace) - }) - }) - it('creates a new service account', () => { cy.visit(sa.path) cy.get('@apiowner').then(({ serviceAccount }: any) => { @@ -102,7 +101,7 @@ it('Verify gwa gateway publish multiple config file', () => { cy.get('@api').then(({ organization }: any) => { cy.setHeaders(organization.headers) cy.setAuthorizationToken(userSession) - cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/' + organization.orgExpectedList.name + '/namespaces/' + nameSpace, 'PUT').then((response:any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/' + organization.orgExpectedList.name + '/gateways/' + nameSpace, 'PUT').then((response:any) => { expect(response.apiRes.status).to.be.equal(200) }) }) diff --git a/e2e/cypress/tests/01-api-key/02-team-access.cy.ts b/e2e/cypress/tests/01-api-key/02-team-access.cy.ts index 40c96c291..7e81407a4 100644 --- a/e2e/cypress/tests/01-api-key/02-team-access.cy.ts +++ b/e2e/cypress/tests/01-api-key/02-team-access.cy.ts @@ -6,6 +6,7 @@ describe('Team Access Spec', () => { const login = new LoginPage() const home = new HomePage() const na = new NamespaceAccessPage() + let userSession: any before(() => { cy.visit('/') @@ -17,15 +18,14 @@ describe('Team Access Spec', () => { cy.preserveCookies() cy.fixture('apiowner').as('apiowner') cy.fixture('common-testdata').as('common-testdata') - // cy.visit(login.path) }) - + it('authenticates Janis (api owner)', () => { cy.get('@apiowner').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { - cy.login(user.credentials.username, user.credentials.password) - cy.log('Logged in!') - home.useNamespace(namespace) + cy.getUserSessionTokenValue(namespace, true).then((value) => { + userSession = value + }) }) }) }) diff --git a/e2e/cypress/tests/01-api-key/03-request-access-inactive-env.cy.ts b/e2e/cypress/tests/01-api-key/03-request-access-inactive-env.cy.ts index 5b9ca7ba5..d567f71eb 100644 --- a/e2e/cypress/tests/01-api-key/03-request-access-inactive-env.cy.ts +++ b/e2e/cypress/tests/01-api-key/03-request-access-inactive-env.cy.ts @@ -30,7 +30,7 @@ describe('Change an Active environment to Inactive', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) @@ -129,7 +129,7 @@ describe('Change an the environment back to active', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) diff --git a/e2e/cypress/tests/01-api-key/05-review-request-without-collecting-credentials.cy.ts b/e2e/cypress/tests/01-api-key/05-review-request-without-collecting-credentials.cy.ts index ec1ac793b..a544a11de 100644 --- a/e2e/cypress/tests/01-api-key/05-review-request-without-collecting-credentials.cy.ts +++ b/e2e/cypress/tests/01-api-key/05-review-request-without-collecting-credentials.cy.ts @@ -27,7 +27,7 @@ describe('Approve Pending Request without collecting credentials Spec', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.get('@access-manager').then(({ user }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/01-api-key/07-approve-pending-rqst.cy.ts b/e2e/cypress/tests/01-api-key/07-approve-pending-rqst.cy.ts index d20c171f1..5916edf77 100644 --- a/e2e/cypress/tests/01-api-key/07-approve-pending-rqst.cy.ts +++ b/e2e/cypress/tests/01-api-key/07-approve-pending-rqst.cy.ts @@ -37,7 +37,7 @@ describe('Approve Pending Request Spec', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.get('@access-manager').then(({ user }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/01-api-key/08-grant-access.cy.ts b/e2e/cypress/tests/01-api-key/08-grant-access.cy.ts index b405ae971..d41e87914 100644 --- a/e2e/cypress/tests/01-api-key/08-grant-access.cy.ts +++ b/e2e/cypress/tests/01-api-key/08-grant-access.cy.ts @@ -27,7 +27,7 @@ describe('Grant Access Spec', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.get('@access-manager').then(({ user }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/01-api-key/10-revoke-access.cy.ts b/e2e/cypress/tests/01-api-key/10-revoke-access.cy.ts index 1a3751718..7f8509cc3 100644 --- a/e2e/cypress/tests/01-api-key/10-revoke-access.cy.ts +++ b/e2e/cypress/tests/01-api-key/10-revoke-access.cy.ts @@ -27,7 +27,7 @@ describe('Revoke product environment access for Kong API spec', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.get('@access-manager').then(({ user }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/02-client-credential-flow/01-client-cred-team-access.cy.ts b/e2e/cypress/tests/02-client-credential-flow/01-client-cred-team-access.cy.ts index 29b770c58..5e9bc365e 100644 --- a/e2e/cypress/tests/02-client-credential-flow/01-client-cred-team-access.cy.ts +++ b/e2e/cypress/tests/02-client-credential-flow/01-client-cred-team-access.cy.ts @@ -24,37 +24,24 @@ describe('Grant appropriate permissions to team members for client credential fl }) it('authenticates Janis (api owner) to get the user session token', () => { - cy.get('@apiowner').then(({ apiTest }: any) => { - cy.getUserSessionTokenValue(apiTest.namespace, false).then((value) => { + cy.get('@apiowner').then(({ clientCredentials }: any) => { + cy.getUserSessionTokenValue(clientCredentials.namespace, false).then((value) => { userSession = value }) }) }) - it('Set token with gwa config command', () => { - cy.exec('gwa config set --token ' + userSession, { timeout: 3000, failOnNonZeroExit: false }).then((response) => { - expect(response.stdout).to.contain("Config settings saved") - }); - }) - - it('create namespace using gwa cli command', () => { - var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); - cy.exec('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 5000, failOnNonZeroExit: false }).then((response) => { - debugger - assert.isNotNaN(response.stdout) - namespace = response.stdout + it('Create new namespace and activate it', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + cy.log('New namespace created: ' + namespace) cy.replaceWordInJsonObject('ccplatform', namespace, 'cc-service-gwa.yml') cy.updateJsonValue('common-testdata.json', 'clientCredentials.namespace', namespace) - // cy.updateJsonValue('apiowner.json', 'clientCredentials.clientIdSecret.product.environment.name.config.serviceName', 'cc-service-for-' + namespace) - cy.executeCliCommand("gwa config set --namespace " + namespace) - }); - }) - - it('activates new namespace', () => { - home.useNamespace(namespace) + cy.updateJsonValue('apiowner.json', 'clientCredentials.namespace', namespace) + cy.activateGateway(namespace) + }) }) - it('Grant namespace access to access manager(Mark)', () => { cy.get('@apiowner').then(({ clientCredentials }: any) => { cy.visit(na.path) diff --git a/e2e/cypress/tests/02-client-credential-flow/02-create_authorizarion_profile.cy.ts b/e2e/cypress/tests/02-client-credential-flow/02-create_authorizarion_profile.cy.ts index 117465051..03533b484 100644 --- a/e2e/cypress/tests/02-client-credential-flow/02-create_authorizarion_profile.cy.ts +++ b/e2e/cypress/tests/02-client-credential-flow/02-create_authorizarion_profile.cy.ts @@ -32,7 +32,7 @@ describe('Generate Authorization Profiles', () => { it('Select the namespace created for client credential ', () => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) diff --git a/e2e/cypress/tests/02-client-credential-flow/03-client-cred-create-api-prod-auth-pro.cy.ts b/e2e/cypress/tests/02-client-credential-flow/03-client-cred-create-api-prod-auth-pro.cy.ts index fe2ab6796..00e9fa540 100644 --- a/e2e/cypress/tests/02-client-credential-flow/03-client-cred-create-api-prod-auth-pro.cy.ts +++ b/e2e/cypress/tests/02-client-credential-flow/03-client-cred-create-api-prod-auth-pro.cy.ts @@ -31,8 +31,8 @@ describe('Create API, Product, and Authorization Profiles; Apply Auth Profiles t }) it('authenticates Janis (api owner) to get the user session token', () => { - cy.get('@common-testdata').then(({ apiTest }: any) => { - cy.getUserSessionTokenValue(apiTest.namespace, false).then((value) => { + cy.get('@common-testdata').then(({ clientCredentials }: any) => { + cy.getUserSessionTokenValue(clientCredentials.namespace, false).then((value) => { userSession = value }) }) @@ -42,10 +42,10 @@ describe('Create API, Product, and Authorization Profiles; Apply Auth Profiles t cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { nameSpace = clientCredentials.namespace - home.useNamespace(clientCredentials.namespace) - cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] - }) + cy.activateGateway(clientCredentials.namespace) + // cy.get('@login').then(function (xhr: any) { + // userSession = xhr.headers['x-auth-request-access-token'] + // }) }) }) }) @@ -83,7 +83,7 @@ describe('Create API, Product, and Authorization Profiles; Apply Auth Profiles t cy.get('@api').then(({ organization }: any) => { cy.setHeaders(organization.headers) cy.setAuthorizationToken(userSession) - cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/' + organization.orgExpectedList.name + '/namespaces/' + nameSpace, 'PUT').then((response:any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/' + organization.orgExpectedList.name + '/gateways/' + nameSpace, 'PUT').then((response:any) => { expect(response.apiRes.status).to.be.equal(200) }) }) diff --git a/e2e/cypress/tests/02-client-credential-flow/05-cids-access-approve-api-rqst.cy.ts b/e2e/cypress/tests/02-client-credential-flow/05-cids-access-approve-api-rqst.cy.ts index 7c2b38156..267711176 100644 --- a/e2e/cypress/tests/02-client-credential-flow/05-cids-access-approve-api-rqst.cy.ts +++ b/e2e/cypress/tests/02-client-credential-flow/05-cids-access-approve-api-rqst.cy.ts @@ -28,7 +28,7 @@ describe('Access manager approves developer access request for Client ID/Secret cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) }) diff --git a/e2e/cypress/tests/02-client-credential-flow/07-deselect-scope.ts b/e2e/cypress/tests/02-client-credential-flow/07-deselect-scope.ts index 42f58f8b6..a48e13e65 100644 --- a/e2e/cypress/tests/02-client-credential-flow/07-deselect-scope.ts +++ b/e2e/cypress/tests/02-client-credential-flow/07-deselect-scope.ts @@ -29,7 +29,7 @@ describe('Deselect the scope from authorization tab', () => { cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { cy.login(user.credentials.username, user.credentials.password).then(() => { - home.useNamespace(clientCredentials.namespace); + cy.activateGateway(clientCredentials.namespace); }) }) }) diff --git a/e2e/cypress/tests/02-client-credential-flow/08-verify-client-scope-in-default-list.ts b/e2e/cypress/tests/02-client-credential-flow/08-verify-client-scope-in-default-list.ts index 221c38808..f758ab802 100644 --- a/e2e/cypress/tests/02-client-credential-flow/08-verify-client-scope-in-default-list.ts +++ b/e2e/cypress/tests/02-client-credential-flow/08-verify-client-scope-in-default-list.ts @@ -81,7 +81,7 @@ describe('Verify the selected client scoped is not displayed in assigned default // cy.get('@apiowner').then(({ clientCredentials }: any) => { // cy.get('@access-manager').then(({ user }: any) => { // cy.login(user.credentials.username, user.credentials.password) -// home.useNamespace(clientCredentials.namespace); +// cy.activateGateway(clientCredentials.namespace); // }) // }) // }) diff --git a/e2e/cypress/tests/02-client-credential-flow/10-jwt-genkp-access-approve-api-rqst.cy.ts b/e2e/cypress/tests/02-client-credential-flow/10-jwt-genkp-access-approve-api-rqst.cy.ts index 6bc97f8ee..32ac0a20f 100644 --- a/e2e/cypress/tests/02-client-credential-flow/10-jwt-genkp-access-approve-api-rqst.cy.ts +++ b/e2e/cypress/tests/02-client-credential-flow/10-jwt-genkp-access-approve-api-rqst.cy.ts @@ -26,7 +26,7 @@ describe('Access manager approves developer access request for JWT - Generated K cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) }) @@ -60,7 +60,7 @@ describe('Make an API request using JWT signed with private key', () => { let alg = 'RS256' let claims = { - aud: Cypress.env('OIDC_ISSUER') + '/auth/realms/master', + aud: Cypress.env('OIDC_ISSUER'), } let jwt = njwt @@ -78,7 +78,8 @@ describe('Make an API request using JWT signed with private key', () => { grant_type: 'client_credentials', client_id: clientId, scopes: 'openid', - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', + client_assertion_type: + 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', client_assertion: jwt, }, form: true, diff --git a/e2e/cypress/tests/02-client-credential-flow/12-jwks-url-access-approval-api-rqst.cy.ts b/e2e/cypress/tests/02-client-credential-flow/12-jwks-url-access-approval-api-rqst.cy.ts index 6350d1f99..6d53773ee 100644 --- a/e2e/cypress/tests/02-client-credential-flow/12-jwks-url-access-approval-api-rqst.cy.ts +++ b/e2e/cypress/tests/02-client-credential-flow/12-jwks-url-access-approval-api-rqst.cy.ts @@ -27,7 +27,7 @@ describe('Access manager approves developer access request for JWKS URL flow', ( cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) }) diff --git a/e2e/cypress/tests/02-client-credential-flow/14-jwt-publlicKey-access-approve-api-rqst.cy.ts b/e2e/cypress/tests/02-client-credential-flow/14-jwt-publlicKey-access-approve-api-rqst.cy.ts index f9ee17ad9..698f1b736 100644 --- a/e2e/cypress/tests/02-client-credential-flow/14-jwt-publlicKey-access-approve-api-rqst.cy.ts +++ b/e2e/cypress/tests/02-client-credential-flow/14-jwt-publlicKey-access-approve-api-rqst.cy.ts @@ -26,7 +26,7 @@ describe('Access manager approves developer access request for JWT - Generated K cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) }) @@ -51,53 +51,56 @@ describe('Access manager approves developer access request for JWT - Generated K describe('Make an API request using JWT signed with private key', () => { it('Get access token using JWT key pair; make sure API calls successfully', () => { cy.readFile('cypress/fixtures/state/store.json').then((store_res) => { - cy.readFile('cypress/fixtures/state/jwtReGenPrivateKey_new.pem').then((privateKey) => { - let jwkCred = JSON.parse(store_res.jwksurlcredentials) - let clientId = jwkCred.clientId - let tokenEndpoint = jwkCred.tokenEndpoint + cy.readFile('cypress/fixtures/state/jwtReGenPrivateKey_new.pem').then( + (privateKey) => { + let jwkCred = JSON.parse(store_res.jwksurlcredentials) + let clientId = jwkCred.clientId + let tokenEndpoint = jwkCred.tokenEndpoint - let now = Math.floor(new Date().getTime() / 1000) - let plus5Minutes = new Date((now + 5 * 60) * 1000) - let alg = 'RS256' + let now = Math.floor(new Date().getTime() / 1000) + let plus5Minutes = new Date((now + 5 * 60) * 1000) + let alg = 'RS256' - let claims = { - aud: Cypress.env('OIDC_ISSUER') + '/auth/realms/master', - } + let claims = { + aud: Cypress.env('OIDC_ISSUER'), + } - let jwt = njwt - .create(claims, privateKey, alg) - .setIssuedAt(now) - .setExpiration(plus5Minutes) - .setIssuer(clientId) - .setSubject(clientId) - .compact() + let jwt = njwt + .create(claims, privateKey, alg) + .setIssuedAt(now) + .setExpiration(plus5Minutes) + .setIssuer(clientId) + .setSubject(clientId) + .compact() - cy.request({ - url: tokenEndpoint, - method: 'POST', - body: { - grant_type: 'client_credentials', - client_id: clientId, - scopes: 'openid', - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - client_assertion: jwt, - }, - form: true, - }).then((res) => { - let token = res.body.access_token cy.request({ - url: Cypress.env('KONG_URL'), - headers: { - Host: 'cc-service-for-platform.api.gov.bc.ca', - }, - auth: { - bearer: token, + url: tokenEndpoint, + method: 'POST', + body: { + grant_type: 'client_credentials', + client_id: clientId, + scopes: 'openid', + client_assertion_type: + 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', + client_assertion: jwt, }, + form: true, }).then((res) => { - expect(res.status).to.eq(200) + let token = res.body.access_token + cy.request({ + url: Cypress.env('KONG_URL'), + headers: { + Host: 'cc-service-for-platform.api.gov.bc.ca', + }, + auth: { + bearer: token, + }, + }).then((res) => { + expect(res.status).to.eq(200) + }) }) - }) - }) + } + ) }) }) }) diff --git a/e2e/cypress/tests/03-manage-labels/02-approve-pending-rqst-for-labels.spec.cy.ts b/e2e/cypress/tests/03-manage-labels/02-approve-pending-rqst-for-labels.spec.cy.ts index 7ff4da239..e76ffc8c7 100644 --- a/e2e/cypress/tests/03-manage-labels/02-approve-pending-rqst-for-labels.spec.cy.ts +++ b/e2e/cypress/tests/03-manage-labels/02-approve-pending-rqst-for-labels.spec.cy.ts @@ -28,7 +28,7 @@ describe('Approve Pending Request Spec', () => { cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/03-manage-labels/03-filter-labels.cy.ts b/e2e/cypress/tests/03-manage-labels/03-filter-labels.cy.ts index 7e4f00872..79ee8e86f 100644 --- a/e2e/cypress/tests/03-manage-labels/03-filter-labels.cy.ts +++ b/e2e/cypress/tests/03-manage-labels/03-filter-labels.cy.ts @@ -29,7 +29,7 @@ describe('Filter Manage labels Spec', () => { cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/03-manage-labels/04-manage-labels.cy.ts b/e2e/cypress/tests/03-manage-labels/04-manage-labels.cy.ts index 0fd11e4bc..8c4e8a1b4 100644 --- a/e2e/cypress/tests/03-manage-labels/04-manage-labels.cy.ts +++ b/e2e/cypress/tests/03-manage-labels/04-manage-labels.cy.ts @@ -27,7 +27,7 @@ describe('Manage/Edit labels spec', () => { cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/03-manage-labels/05-link-consumers.ts b/e2e/cypress/tests/03-manage-labels/05-link-consumers.ts index 0d69e9158..978ac6f4d 100644 --- a/e2e/cypress/tests/03-manage-labels/05-link-consumers.ts +++ b/e2e/cypress/tests/03-manage-labels/05-link-consumers.ts @@ -29,7 +29,7 @@ describe('Link Consumers to Namespace', () => { cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/04-gateway-services/01-gateway-service-details.cy.ts b/e2e/cypress/tests/04-gateway-services/01-gateway-service-details.cy.ts index 69c915900..696766a2a 100644 --- a/e2e/cypress/tests/04-gateway-services/01-gateway-service-details.cy.ts +++ b/e2e/cypress/tests/04-gateway-services/01-gateway-service-details.cy.ts @@ -27,7 +27,7 @@ describe('Verify Gateway Service details', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) diff --git a/e2e/cypress/tests/04-gateway-services/02-filter-gateway-service.cy.ts b/e2e/cypress/tests/04-gateway-services/02-filter-gateway-service.cy.ts index df77485fd..4185064dd 100644 --- a/e2e/cypress/tests/04-gateway-services/02-filter-gateway-service.cy.ts +++ b/e2e/cypress/tests/04-gateway-services/02-filter-gateway-service.cy.ts @@ -29,7 +29,7 @@ describe('Filter Gateway Services Spec', () => { cy.get('@apiowner').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) diff --git a/e2e/cypress/tests/05-migrate-user/01-migrate-user-access.cy.ts b/e2e/cypress/tests/05-migrate-user/01-migrate-user-access.cy.ts index 29600e05e..c298be7c9 100644 --- a/e2e/cypress/tests/05-migrate-user/01-migrate-user-access.cy.ts +++ b/e2e/cypress/tests/05-migrate-user/01-migrate-user-access.cy.ts @@ -24,7 +24,7 @@ describe('Assign Access to existing user Spec', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) @@ -68,7 +68,7 @@ describe('Authernticate with old user to initiate migration', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(oldUser.credentials.username, oldUser.credentials.password) cy.log('Logged in!') - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) @@ -105,17 +105,16 @@ describe('Verify that permission of old user is migrated to new user', () => { // it('activates new namespace', () => { // cy.get('@apiowner').then(({ namespace }: any) => { - // home.useNamespace(namespace) + // cy.activateGateway(namespace) // }) // }) it('Get the permission of the user', () => { - cy.getUserSession().then(() => { - cy.get('@common-testdata').then(({ namespace }: any) => { - home.useNamespace(namespace) - cy.get('@login').then(function (xhr: any) { - userScopes = xhr.response.body.user.scopes - }) + cy.get('@common-testdata').then(({ namespace }: any) => { + cy.activateGateway(namespace) + cy.getUserSession() + cy.get('@login').then(function (xhr: any) { + userScopes = xhr.body.user.scopes }) }) }) diff --git a/e2e/cypress/tests/06-refresh-credential/02-client-credentials.cy.ts b/e2e/cypress/tests/06-refresh-credential/02-client-credentials.cy.ts index e7a10e6e4..d159321cb 100644 --- a/e2e/cypress/tests/06-refresh-credential/02-client-credentials.cy.ts +++ b/e2e/cypress/tests/06-refresh-credential/02-client-credentials.cy.ts @@ -31,7 +31,7 @@ import MyAccessPage from '../../pageObjects/myAccess' // cy.get('@access-manager').then(({ user, clientCredentials }: any) => { // cy.get('@developer').then(({ clientCredentials }: any) => { // cy.login(user.credentials.username, user.credentials.password).then(() => { -// home.useNamespace(clientCredentials.namespace); +// cy.activateGateway(clientCredentials.namespace); // cy.visit(consumers.path); // consumers.filterConsumerByTypeAndValue('Environment', clientCredentials.clientIdSecret.product.environment) // consumers.clickOnTheFirstConsumerID() diff --git a/e2e/cypress/tests/07-manage-control/01-ip-restriction.cy.ts b/e2e/cypress/tests/07-manage-control/01-ip-restriction.cy.ts index 4ea8283dc..43c279d7b 100644 --- a/e2e/cypress/tests/07-manage-control/01-ip-restriction.cy.ts +++ b/e2e/cypress/tests/07-manage-control/01-ip-restriction.cy.ts @@ -23,7 +23,7 @@ describe('Manage Control-IP Restriction Spec', () => { cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password).then(() => { - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/07-manage-control/02-rate-limiting.cy.ts b/e2e/cypress/tests/07-manage-control/02-rate-limiting.cy.ts index 6b99859b4..292b9eded 100644 --- a/e2e/cypress/tests/07-manage-control/02-rate-limiting.cy.ts +++ b/e2e/cypress/tests/07-manage-control/02-rate-limiting.cy.ts @@ -28,7 +28,7 @@ describe('Manage Control-Rate Limiting Spec for Service as Scope and Local Polic cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password).then(() => { - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/07-manage-control/03-kong-api-only-apply-rate-limiting.cy.ts b/e2e/cypress/tests/07-manage-control/03-kong-api-only-apply-rate-limiting.cy.ts index 08767f570..8de053b68 100644 --- a/e2e/cypress/tests/07-manage-control/03-kong-api-only-apply-rate-limiting.cy.ts +++ b/e2e/cypress/tests/07-manage-control/03-kong-api-only-apply-rate-limiting.cy.ts @@ -42,7 +42,7 @@ describe('Apply Kong API key only plugin', () => { cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ namespace }: any) => { nameSpace = namespace - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) @@ -204,7 +204,7 @@ describe('Approve Pending Request Spec', () => { cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/08-client-role/02-add-roles-authorization-profile.ts b/e2e/cypress/tests/08-client-role/02-add-roles-authorization-profile.ts index e6ed788f7..c37fcc880 100644 --- a/e2e/cypress/tests/08-client-role/02-add-roles-authorization-profile.ts +++ b/e2e/cypress/tests/08-client-role/02-add-roles-authorization-profile.ts @@ -33,7 +33,7 @@ describe('Apply client roles to the Authorization Profile', () => { it('Select the namespace created for client credential ', () => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) diff --git a/e2e/cypress/tests/08-client-role/03-read-client-role.ts b/e2e/cypress/tests/08-client-role/03-read-client-role.ts index cad5ca94d..eebd440b5 100644 --- a/e2e/cypress/tests/08-client-role/03-read-client-role.ts +++ b/e2e/cypress/tests/08-client-role/03-read-client-role.ts @@ -80,7 +80,7 @@ describe('Access manager apply "Read" role and approves developer access request cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) }) diff --git a/e2e/cypress/tests/08-client-role/04-write-client-role.ts b/e2e/cypress/tests/08-client-role/04-write-client-role.ts index 411fa8404..70ac86ea3 100644 --- a/e2e/cypress/tests/08-client-role/04-write-client-role.ts +++ b/e2e/cypress/tests/08-client-role/04-write-client-role.ts @@ -79,7 +79,7 @@ describe('Access manager apply "Write" role and approves developer access reques cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) }) diff --git a/e2e/cypress/tests/08-client-role/05-check-without-role.ts b/e2e/cypress/tests/08-client-role/05-check-without-role.ts index a96f1d6a0..d13c51c1e 100644 --- a/e2e/cypress/tests/08-client-role/05-check-without-role.ts +++ b/e2e/cypress/tests/08-client-role/05-check-without-role.ts @@ -43,7 +43,7 @@ describe('Reset Authorization profile to default (without any role)', () => { cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { nameSpace = clientCredentials.namespace - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) }) diff --git a/e2e/cypress/tests/09-update-product-env/01-client-credential-to-kong-acl-api.cy.ts b/e2e/cypress/tests/09-update-product-env/01-client-credential-to-kong-acl-api.cy.ts index e9b7d910b..83a1a7a7a 100644 --- a/e2e/cypress/tests/09-update-product-env/01-client-credential-to-kong-acl-api.cy.ts +++ b/e2e/cypress/tests/09-update-product-env/01-client-credential-to-kong-acl-api.cy.ts @@ -43,9 +43,9 @@ describe('Change Authorization profile', () => { cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { nameSpace = clientCredentials.namespace - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] + userSession = xhr.headers['x-auth-request-access-token'] }) }) }) @@ -182,7 +182,7 @@ describe('Access manager approves developer access request for Kong API ACL auth cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) }) }) }) diff --git a/e2e/cypress/tests/09-update-product-env/02-kong-acl-api-to-client-credential.cy.ts b/e2e/cypress/tests/09-update-product-env/02-kong-acl-api-to-client-credential.cy.ts index 14f622a41..ebd18aa32 100644 --- a/e2e/cypress/tests/09-update-product-env/02-kong-acl-api-to-client-credential.cy.ts +++ b/e2e/cypress/tests/09-update-product-env/02-kong-acl-api-to-client-credential.cy.ts @@ -46,7 +46,7 @@ describe('Change Authorization profile from Kong ACL-API to Client Credential', cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ namespace }: any) => { nameSpace = namespace - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) @@ -188,7 +188,7 @@ describe('Access manager approves developer access request for Client ID/Secret cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) diff --git a/e2e/cypress/tests/09-update-product-env/03-apply-multiple-services.cy.ts b/e2e/cypress/tests/09-update-product-env/03-apply-multiple-services.cy.ts index 50de65e81..2537f4c7c 100644 --- a/e2e/cypress/tests/09-update-product-env/03-apply-multiple-services.cy.ts +++ b/e2e/cypress/tests/09-update-product-env/03-apply-multiple-services.cy.ts @@ -48,7 +48,7 @@ describe('Apply multiple services to the product environment', () => { cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ namespace }: any) => { nameSpace = namespace - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) @@ -215,7 +215,7 @@ describe('Access manager approves developer access request for Client ID/Secret cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) diff --git a/e2e/cypress/tests/09-update-product-env/04-change-env-status copy.cy.ts b/e2e/cypress/tests/09-update-product-env/04-change-env-status copy.cy.ts index 0eb83a20d..746eb9d7f 100644 --- a/e2e/cypress/tests/09-update-product-env/04-change-env-status copy.cy.ts +++ b/e2e/cypress/tests/09-update-product-env/04-change-env-status copy.cy.ts @@ -47,7 +47,7 @@ describe('Change Product environment from active to inactive', () => { cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ namespace }: any) => { nameSpace = namespace - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) diff --git a/e2e/cypress/tests/09-update-product-env/06-shared-idp.cy.ts b/e2e/cypress/tests/09-update-product-env/06-shared-idp.cy.ts index 5f5744773..42ce5c3dc 100644 --- a/e2e/cypress/tests/09-update-product-env/06-shared-idp.cy.ts +++ b/e2e/cypress/tests/09-update-product-env/06-shared-idp.cy.ts @@ -11,10 +11,9 @@ import Products from '../../pageObjects/products' describe('Apply Shared IDP while creating Authorization Profile', () => { const login = new LoginPage() - var nameSpace: string const home = new HomePage() const authProfile = new AuthorizationProfile() - let userSession: string + let userSession: any before(() => { cy.visit('/') @@ -32,15 +31,18 @@ describe('Apply Shared IDP while creating Authorization Profile', () => { cy.visit(login.path) }) - it('authenticates Janis (api owner) to get the user session token', () => { + it('Authenticates api owner', () => { + cy.get('@apiowner').then(({ user }: any) => { + cy.login(user.credentials.username, user.credentials.password) + }) + }) + + it('Activates the namespace', () => { cy.getUserSession().then(() => { - cy.get('@apiowner').then(({ user }: any) => { - cy.get('@common-testdata').then(({ namespace }: any) => { - cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace) - cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] - }) + cy.get('@common-testdata').then(({ namespace }: any) => { + cy.activateGateway(namespace) + cy.get('@login').then(function (xhr: any) { + userSession = xhr.headers['x-auth-request-access-token'] }) }) }) @@ -56,7 +58,7 @@ describe('Apply Shared IDP while creating Authorization Profile', () => { it('Publish the Shared IDP profile', () => { cy.get('@common-testdata').then(({ namespace }: any) => { - cy.makeAPIRequest('ds/api/v2/namespaces/' + namespace + '/issuers', 'PUT').then((response:any) => { + cy.makeAPIRequest('ds/api/v3/gateways/' + namespace + '/issuers', 'PUT').then((response:any) => { expect(response.apiRes.status).to.be.equal(200) expect(response.apiRes.body.result).to.be.contain('created') }) @@ -87,6 +89,7 @@ describe('Update IDP issuer for shared IDP profile', () => { before(() => { cy.visit('/') + cy.reload(true) }) beforeEach(() => { @@ -97,15 +100,18 @@ describe('Update IDP issuer for shared IDP profile', () => { cy.visit(login.path) }) - it('authenticates Janis (api owner) to get the user session token', () => { + it('Authenticates api owner', () => { + cy.get('@apiowner').then(({ user }: any) => { + cy.login(user.credentials.username, user.credentials.password) + }) + }) + + it('Activates the namespace', () => { cy.getUserSession().then(() => { - cy.get('@apiowner').then(({ user }: any) => { - cy.get('@common-testdata').then(({ namespace }: any) => { - cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace) - cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] - }) + cy.get('@common-testdata').then(({ namespace }: any) => { + cy.activateGateway(namespace) + cy.get('@login').then(function (xhr: any) { + userSession = xhr.headers['x-auth-request-access-token'] }) }) }) @@ -121,7 +127,7 @@ describe('Update IDP issuer for shared IDP profile', () => { it('Put the resource and verify the success code in the response', () => { cy.get('@common-testdata').then(({ namespace }: any) => { - cy.makeAPIRequest('ds/api/v2/namespaces/' + namespace + '/issuers', 'PUT').then((response:any) => { + cy.makeAPIRequest('ds/api/v3/gateways/' + namespace + '/issuers', 'PUT').then((response:any) => { expect(response.apiRes.status).to.be.equal(200) }) }) diff --git a/e2e/cypress/tests/09-update-product-env/07-kong-public-auth.ts b/e2e/cypress/tests/09-update-product-env/07-kong-public-auth.ts index 4db1d8d33..70074aec2 100644 --- a/e2e/cypress/tests/09-update-product-env/07-kong-public-auth.ts +++ b/e2e/cypress/tests/09-update-product-env/07-kong-public-auth.ts @@ -43,9 +43,9 @@ describe('Verify for Kong Public Auth', () => { cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { nameSpace = clientCredentials.namespace - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] + userSession = xhr.headers['x-auth-request-access-token'] }) }) }) diff --git a/e2e/cypress/tests/09-update-product-env/08-protected-externally.ts b/e2e/cypress/tests/09-update-product-env/08-protected-externally.ts index ff84052df..de5667207 100644 --- a/e2e/cypress/tests/09-update-product-env/08-protected-externally.ts +++ b/e2e/cypress/tests/09-update-product-env/08-protected-externally.ts @@ -34,9 +34,9 @@ describe('Verify Protected Externally Auth', () => { cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { nameSpace = clientCredentials.namespace - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] + userSession = xhr.headers['x-auth-request-access-token'] }) }) }) diff --git a/e2e/cypress/tests/09-update-product-env/09-two-tiered-hidden.cy.ts b/e2e/cypress/tests/09-update-product-env/09-two-tiered-hidden.cy.ts index b103e7afd..b02bf3447 100644 --- a/e2e/cypress/tests/09-update-product-env/09-two-tiered-hidden.cy.ts +++ b/e2e/cypress/tests/09-update-product-env/09-two-tiered-hidden.cy.ts @@ -50,9 +50,7 @@ describe('Verify Two Tiered Hidden', () => { it('create namespace with cli', () => { cy.get('@common-testdata').then(({ twoTieredHidden }: any) => { cy.executeCliCommand( - 'gwa namespace create --name ' + - twoTieredHidden.namespace + - ' --description="Two Tiered Hidden"' + 'gwa gateway create --gateway-id ' + twoTieredHidden.namespace + ' --display-name="Two Tiered Hidden"' ).then((response) => { expect(response.stdout).to.contain(twoTieredHidden.namespace) }) @@ -69,9 +67,9 @@ describe('Verify Two Tiered Hidden', () => { cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ twoTieredHidden }: any) => { nameSpace = twoTieredHidden.namespace - home.useNamespace(twoTieredHidden.namespace) + cy.activateGateway(twoTieredHidden.namespace) cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] + userSession = xhr.headers['x-auth-request-access-token'] }) }) }) diff --git a/e2e/cypress/tests/10-clear-resources/01-create-api.cy.ts b/e2e/cypress/tests/10-clear-resources/01-create-api.cy.ts index d7c126c42..7695b898c 100644 --- a/e2e/cypress/tests/10-clear-resources/01-create-api.cy.ts +++ b/e2e/cypress/tests/10-clear-resources/01-create-api.cy.ts @@ -40,20 +40,19 @@ describe('Create API Spec for Delete Resources', () => { }); }) - it('create namespace using gwa cli command', () => { - var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); - cy.exec('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 3000, failOnNonZeroExit: false }).then((response) => { - assert.isNotNaN(response.stdout) - namespace = response.stdout + it('create namespace', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + cy.log('New namespace created: ' + namespace) cy.replaceWordInJsonObject('ns.deleteplatform', 'ns.' + namespace, 'service-clear-resources-gwa.yml') cy.updateJsonValue('common-testdata.json', 'deleteResources.namespace', namespace) // cy.updateJsonValue('apiowner.json', 'clientCredentials.clientIdSecret.product.environment.name.config.serviceName', 'cc-service-for-' + namespace) - cy.executeCliCommand("gwa config set --namespace " + namespace) + cy.executeCliCommand("gwa config set --gateway " + namespace) }); }) it('activates new namespace', () => { - home.useNamespace(namespace) + cy.activateGateway(namespace) }) it('creates a new service account', () => { @@ -83,7 +82,7 @@ describe('Create API Spec for Delete Resources', () => { cy.get('@api').then(({ organization }: any) => { cy.setHeaders(organization.headers) cy.setAuthorizationToken(userSession) - cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/' + organization.orgExpectedList.name + '/namespaces/' + namespace, 'PUT').then((response:any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/' + organization.orgExpectedList.name + '/gateways/' + namespace, 'PUT').then((response:any) => { expect(response.apiRes.status).to.be.equal(200) }) }) diff --git a/e2e/cypress/tests/10-clear-resources/02-team-access.cy.ts b/e2e/cypress/tests/10-clear-resources/02-team-access.cy.ts index 0b9d69f7c..7b2851bff 100644 --- a/e2e/cypress/tests/10-clear-resources/02-team-access.cy.ts +++ b/e2e/cypress/tests/10-clear-resources/02-team-access.cy.ts @@ -24,7 +24,7 @@ describe('Team Access Spec', () => { cy.get('@common-testdata').then(({ deleteResources }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(deleteResources.namespace) + cy.activateGateway(deleteResources.namespace) }) }) }) diff --git a/e2e/cypress/tests/10-clear-resources/04-delete-consumer.ts b/e2e/cypress/tests/10-clear-resources/04-delete-consumer.ts index b29d95b28..1e50e1e85 100644 --- a/e2e/cypress/tests/10-clear-resources/04-delete-consumer.ts +++ b/e2e/cypress/tests/10-clear-resources/04-delete-consumer.ts @@ -31,7 +31,7 @@ describe('Delete created consumer', () => { cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ deleteResources }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(deleteResources.namespace); + cy.activateGateway(deleteResources.namespace); }) }) }) diff --git a/e2e/cypress/tests/10-clear-resources/05-delete-resources.cy.ts b/e2e/cypress/tests/10-clear-resources/05-delete-resources.cy.ts index 6a58f6ed9..855994ad4 100644 --- a/e2e/cypress/tests/10-clear-resources/05-delete-resources.cy.ts +++ b/e2e/cypress/tests/10-clear-resources/05-delete-resources.cy.ts @@ -4,6 +4,8 @@ import NameSpacePage from '../../pageObjects/namespace' import Products from '../../pageObjects/products' import ServiceAccountsPage from '../../pageObjects/serviceAccounts' +const cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); + describe('Delete created resources', () => { const login = new LoginPage() const home = new HomePage() @@ -11,6 +13,7 @@ describe('Delete created resources', () => { const pd = new Products() const ns = new NameSpacePage let flag: boolean + let userSession: any before(() => { cy.visit('/') @@ -29,7 +32,10 @@ describe('Delete created resources', () => { cy.get('@apiowner').then(({ user }: any) => { cy.get('@common-testdata').then(({ deleteResources }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(deleteResources.namespace); + cy.activateGateway(deleteResources.namespace); + cy.getUserSessionTokenValue(deleteResources.namespace, false).then((value) => { + userSession = value + }) }) }) }) @@ -57,26 +63,53 @@ describe('Delete created resources', () => { sa.deleteAllServiceAccounts() }) - it('Delete Namespace', () => { - cy.get('@common-testdata').then(({ deleteResources }: any) => { - cy.visit(ns.path) - ns.deleteNamespace(deleteResources.namespace) - }) + it('Set token with gwa config command', () => { + cy.exec('gwa config set --token ' + userSession, { timeout: 3000, failOnNonZeroExit: false }).then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); }) - it('Verify that the deleted namespace does not display in namespace list', () => { - cy.on('fail', (err, runnable) => { - flag = false - }) - cy.get('@common-testdata').then(({ deleteResources }: any) => { - flag = true - home.useNamespace(deleteResources.namespace) - }) + it('Set environment with gwa config command', () => { + cy.executeCliCommand('gwa config set --host ' + cleanedUrl + ' --scheme http').then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); }) - it('Verify that the namespace is deleted', () => { - assert.equal(flag, false) - }) + it('Delete Namespace (CLI)', () => { + cy.get('@common-testdata').then(({ deleteResources }: any) => { + cy.deleteGatewayCli(deleteResources.namespace, true) + }); + }); + + it('Verify that namespace is no longer available', () => { + cy.get('@common-testdata').then(({ deleteResources }: any) => { + cy.visit('/') + cy.wrap(null).then(() => { + return cy.getGateways(); + }).then((result) => { + const namespaceNames = result.map((ns: { name: any }) => ns.name); + expect(namespaceNames).to.not.include(deleteResources.namespace); + }); + }); + }); + + it('Verify delete namespace works from the UI', () => { + cy.createGateway().then((response) => { + const namespace = response.gatewayId + cy.log('New namespace created: ' + namespace) + cy.activateGateway(namespace); + cy.visit(ns.detailPath) + ns.deleteNamespace(namespace) + + cy.visit('/') + cy.wrap(null).then(() => { + return cy.getGateways(); + }).then((result) => { + const namespaceNames = result.map((ns: { name: any }) => ns.name); + expect(namespaceNames).to.not.include(namespace); + }); + }); + }); after(() => { cy.logout() diff --git a/e2e/cypress/tests/10-clear-resources/06-delete-service-acc.ts b/e2e/cypress/tests/10-clear-resources/06-delete-service-acc.ts index 2a82d2e05..b334b55c5 100644 --- a/e2e/cypress/tests/10-clear-resources/06-delete-service-acc.ts +++ b/e2e/cypress/tests/10-clear-resources/06-delete-service-acc.ts @@ -32,7 +32,7 @@ describe('Create API Spec', () => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) }) diff --git a/e2e/cypress/tests/11-activity-feed/01-activity-feed.cy.ts b/e2e/cypress/tests/11-activity-feed/01-activity-feed.cy.ts index cae68ff2d..6dd68051b 100644 --- a/e2e/cypress/tests/11-activity-feed/01-activity-feed.cy.ts +++ b/e2e/cypress/tests/11-activity-feed/01-activity-feed.cy.ts @@ -108,7 +108,7 @@ describe('Verify the Activity filter for users', () => { it('activates new namespace', () => { cy.get('@common-testdata').then(({ namespace }: any) => { - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) diff --git a/e2e/cypress/tests/11-activity-feed/02-activity-feed-failure.cy.ts b/e2e/cypress/tests/11-activity-feed/02-activity-feed-failure.cy.ts index 74c4a2e8e..047de8ab6 100644 --- a/e2e/cypress/tests/11-activity-feed/02-activity-feed-failure.cy.ts +++ b/e2e/cypress/tests/11-activity-feed/02-activity-feed-failure.cy.ts @@ -86,9 +86,9 @@ describe('Create API, Product, and Authorization Profiles; Apply Auth Profiles t cy.getUserSession().then(() => { cy.get('@common-testdata').then(({ clientCredentials }: any) => { nameSpace = clientCredentials.namespace - home.useNamespace(clientCredentials.namespace) + cy.activateGateway(clientCredentials.namespace) cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] + userSession = xhr.headers['x-auth-request-access-token'] }) }) }) diff --git a/e2e/cypress/tests/12-access-permission/01-create-api.cy.ts b/e2e/cypress/tests/12-access-permission/01-create-api.cy.ts index 938ff9685..0f37dd9bc 100644 --- a/e2e/cypress/tests/12-access-permission/01-create-api.cy.ts +++ b/e2e/cypress/tests/12-access-permission/01-create-api.cy.ts @@ -40,20 +40,19 @@ describe('Create API Spec', () => { }); }) - it('create namespace using gwa cli command', () => { - var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); - cy.exec('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 3000, failOnNonZeroExit: false }).then((response) => { - assert.isNotNaN(response.stdout) - namespace = response.stdout + it('create namespace', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + cy.log('New namespace created: ' + namespace) cy.replaceWordInJsonObject('ns.permission', 'ns.' + namespace, 'service-permission-gwa.yml') cy.updateJsonValue('common-testdata.json', 'checkPermission.namespace', namespace) // cy.updateJsonValue('apiowner.json', 'clientCredentials.clientIdSecret.product.environment.name.config.serviceName', 'cc-service-for-' + namespace) - cy.executeCliCommand("gwa config set --namespace " + namespace) + cy.executeCliCommand("gwa config set --gateway " + namespace) }); }) it('activates new namespace', () => { - home.useNamespace(namespace) + cy.activateGateway(namespace) }) it('creates a new service account', () => { @@ -81,7 +80,7 @@ describe('Create API Spec', () => { cy.get('@api').then(({ organization }: any) => { cy.setHeaders(organization.headers) cy.setAuthorizationToken(userSession) - cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/' + organization.orgExpectedList.name + '/namespaces/' + namespace, 'PUT').then((response:any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/' + organization.orgExpectedList.name + '/gateways/' + namespace, 'PUT').then((response:any) => { expect(response.apiRes.status).to.be.equal(200) }) }) diff --git a/e2e/cypress/tests/12-access-permission/02-team-access.cy.ts b/e2e/cypress/tests/12-access-permission/02-team-access.cy.ts index c8bb5898f..834dbcc5b 100644 --- a/e2e/cypress/tests/12-access-permission/02-team-access.cy.ts +++ b/e2e/cypress/tests/12-access-permission/02-team-access.cy.ts @@ -25,7 +25,7 @@ describe('Team Access Spec', () => { cy.get('@common-testdata').then(({ checkPermission }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) + cy.activateGateway(checkPermission.namespace) }) }) }) diff --git a/e2e/cypress/tests/12-access-permission/04-access-manager.cy.ts b/e2e/cypress/tests/12-access-permission/04-access-manager.cy.ts index 2ba9413d2..256459e7a 100644 --- a/e2e/cypress/tests/12-access-permission/04-access-manager.cy.ts +++ b/e2e/cypress/tests/12-access-permission/04-access-manager.cy.ts @@ -1,7 +1,6 @@ import LoginPage from '../../pageObjects/login' import HomePage from '../../pageObjects/home' import NamespaceAccessPage from '../../pageObjects/namespaceAccess' -import MyProfilePage from '../../pageObjects/myProfile' import ConsumersPage from '../../pageObjects/consumers' describe('Grant Access Manager Role', () => { @@ -27,7 +26,7 @@ describe('Grant Access Manager Role', () => { cy.get('@common-testdata').then(({ checkPermission }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) + cy.activateGateway(checkPermission.namespace) }) }) }) @@ -50,7 +49,6 @@ describe('Verify that Mark is able to view the pending request', () => { const login = new LoginPage() const home = new HomePage() const consumers = new ConsumersPage() - const mp = new MyProfilePage() const na = new NamespaceAccessPage() @@ -72,8 +70,7 @@ describe('Verify that Mark is able to view the pending request', () => { cy.visit(login.path) cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) - cy.visit(mp.path) + cy.activateGateway(checkPermission.namespace) }) }) }) diff --git a/e2e/cypress/tests/12-access-permission/05-namespace-manage.cy.ts b/e2e/cypress/tests/12-access-permission/05-namespace-manage.cy.ts index 3075dfe44..66afc0807 100644 --- a/e2e/cypress/tests/12-access-permission/05-namespace-manage.cy.ts +++ b/e2e/cypress/tests/12-access-permission/05-namespace-manage.cy.ts @@ -1,7 +1,6 @@ import LoginPage from '../../pageObjects/login' import HomePage from '../../pageObjects/home' import NameSpacePage from '../../pageObjects/namespace' -import MyProfilePage from '../../pageObjects/myProfile' import ToolBar from '../../pageObjects/toolbar' import AuthorizationProfile from '../../pageObjects/authProfile' import NamespaceAccessPage from '../../pageObjects/namespaceAccess' @@ -29,12 +28,12 @@ describe('Grant Namespace Manage Role', () => { cy.get('@common-testdata').then(({ checkPermission }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) + cy.activateGateway(checkPermission.namespace) }) }) }) - it('Grant only "Namespace.Manage" permission to Wendy', () => { + it('Grant only "Gateway.Manage" permission to Wendy', () => { cy.get('@apiowner').then(({ checkPermission }: any) => { cy.visit(na.path) // na.revokeAllPermission('wendy@idir') @@ -55,7 +54,6 @@ describe('Verify that Wendy is able to see all the options for the Namespace', ( const login = new LoginPage() const home = new HomePage() const ns = new NameSpacePage() - const mp = new MyProfilePage() const tb = new ToolBar() const authProfile = new AuthorizationProfile() @@ -77,14 +75,13 @@ describe('Verify that Wendy is able to see all the options for the Namespace', ( cy.visit(login.path) cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) - cy.visit(mp.path) + cy.activateGateway(checkPermission.namespace) }) }) }) it('Verify that all the namespace options and activities are displayed', () => { - cy.visit(ns.path) + cy.visit(ns.detailPath) ns.verifyThatAllOptionsAreDisplayed() }) diff --git a/e2e/cypress/tests/12-access-permission/06-credential-issuer.cy.ts b/e2e/cypress/tests/12-access-permission/06-credential-issuer.cy.ts index e50d355f4..546f83e40 100644 --- a/e2e/cypress/tests/12-access-permission/06-credential-issuer.cy.ts +++ b/e2e/cypress/tests/12-access-permission/06-credential-issuer.cy.ts @@ -2,7 +2,6 @@ import LoginPage from '../../pageObjects/login' import HomePage from '../../pageObjects/home' import NamespaceAccessPage from '../../pageObjects/namespaceAccess' import NameSpacePage from '../../pageObjects/namespace' -import MyProfilePage from '../../pageObjects/myProfile' import ToolBar from '../../pageObjects/toolbar' import AuthorizationProfile from '../../pageObjects/authProfile' @@ -28,7 +27,7 @@ describe('Grant Credential Issuer Role', () => { cy.get('@common-testdata').then(({ checkPermission }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) + cy.activateGateway(checkPermission.namespace) }) }) }) @@ -53,7 +52,6 @@ describe('Verify that Wendy is able to generate authorization profile', () => { const login = new LoginPage() const home = new HomePage() const ns = new NameSpacePage() - const mp = new MyProfilePage() const tb = new ToolBar() const authProfile = new AuthorizationProfile() @@ -75,14 +73,13 @@ describe('Verify that Wendy is able to generate authorization profile', () => { cy.visit(login.path) cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) - cy.visit(mp.path) + cy.activateGateway(checkPermission.namespace) }) }) }) it('Verify that only Authorization Profile option is displayed in Namespace page', () => { - cy.visit(ns.path) + cy.visit(ns.detailPath) ns.verifyThatOnlyAuthorizationProfileLinkIsExist() }) diff --git a/e2e/cypress/tests/12-access-permission/07-namespace-view.cy.ts b/e2e/cypress/tests/12-access-permission/07-namespace-view.cy.ts index 0acd828a1..6d46e831f 100644 --- a/e2e/cypress/tests/12-access-permission/07-namespace-view.cy.ts +++ b/e2e/cypress/tests/12-access-permission/07-namespace-view.cy.ts @@ -1,6 +1,5 @@ import LoginPage from '../../pageObjects/login' import HomePage from '../../pageObjects/home' -import MyProfilePage from '../../pageObjects/myProfile' import NamespaceAccessPage from '../../pageObjects/namespaceAccess' import ConsumersPage from '../../pageObjects/consumers' import ServiceAccountsPage from '../../pageObjects/serviceAccounts' @@ -29,12 +28,12 @@ describe('Grant Namespace View Role to Mark', () => { cy.get('@common-testdata').then(({ checkPermission }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) + cy.activateGateway(checkPermission.namespace) }) }) }) - it('Grant only "Namespace.View" permission to Mark', () => { + it('Grant only "Gateway.View" permission to Mark', () => { cy.get('@apiowner').then(({ checkPermission }: any) => { cy.visit(na.path) na.revokeAllPermission(checkPermission.grantPermission.Mark.userName) @@ -52,7 +51,6 @@ describe('Verify that Mark is unable to create service account', () => { const login = new LoginPage() const home = new HomePage() - const mp = new MyProfilePage() const consumers = new ConsumersPage() const sa = new ServiceAccountsPage() @@ -75,8 +73,7 @@ describe('Verify that Mark is unable to create service account', () => { cy.visit(login.path) cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) - cy.visit(mp.path) + cy.activateGateway(checkPermission.namespace) }) }) }) diff --git a/e2e/cypress/tests/12-access-permission/08-gateway-config.cy.ts b/e2e/cypress/tests/12-access-permission/08-gateway-config.cy.ts index 0f5b16d00..badeb3e75 100644 --- a/e2e/cypress/tests/12-access-permission/08-gateway-config.cy.ts +++ b/e2e/cypress/tests/12-access-permission/08-gateway-config.cy.ts @@ -1,6 +1,5 @@ import LoginPage from '../../pageObjects/login' import HomePage from '../../pageObjects/home' -import MyProfilePage from '../../pageObjects/myProfile' import NamespaceAccessPage from '../../pageObjects/namespaceAccess' import ServiceAccountsPage from '../../pageObjects/serviceAccounts' import AuthorizationProfile from '../../pageObjects/authProfile' @@ -31,12 +30,12 @@ describe('Grant Gateway Config Role to Wendy', () => { cy.get('@apiowner').then(({ user }: any) => { cy.get('@common-testdata').then(({ checkPermission }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(checkPermission.namespace) + cy.activateGateway(checkPermission.namespace) }) }) }) - it('Grant "GatewayConfig.Publish" and "Namespace.View" access to Wendy (access manager)', () => { + it('Grant "GatewayConfig.Publish" and "Gateway.View" access to Wendy (access manager)', () => { cy.get('@apiowner').then(({ checkPermission }: any) => { cy.visit(na.path) // na.revokePermission(checkPermission.grantPermission.Wendy) @@ -56,7 +55,6 @@ describe('Verify that Wendy is able to generate authorization profile', () => { const login = new LoginPage() const home = new HomePage() const ns = new NameSpacePage() - const mp = new MyProfilePage() const tb = new ToolBar() const authProfile = new AuthorizationProfile() @@ -78,8 +76,7 @@ describe('Verify that Wendy is able to generate authorization profile', () => { cy.visit(login.path) cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(checkPermission.namespace) - cy.visit(mp.path) + cy.activateGateway(checkPermission.namespace) }) }) }) diff --git a/e2e/cypress/tests/12-access-permission/09-content-publish.cy.ts b/e2e/cypress/tests/12-access-permission/09-content-publish.cy.ts index bad3c0f6b..d51a49667 100644 --- a/e2e/cypress/tests/12-access-permission/09-content-publish.cy.ts +++ b/e2e/cypress/tests/12-access-permission/09-content-publish.cy.ts @@ -1,67 +1,69 @@ -import LoginPage from '../../pageObjects/login' -import HomePage from '../../pageObjects/home' - +// import LoginPage from '../../pageObjects/login' +// import HomePage from '../../pageObjects/home' +it('Content publication tests are disabled', () => { + cy.log('Content publication tests are disabled. This feature has been removed.') +}) -describe('Verify Content Publish Permission', () => { - const login = new LoginPage() - const home = new HomePage() - let token: any +// describe('Verify Content Publish Permission', () => { +// const login = new LoginPage() +// const home = new HomePage() +// let token: any - before(() => { - cy.visit('/') - cy.deleteAllCookies() - cy.reload() - }) +// before(() => { +// cy.visit('/') +// cy.deleteAllCookies() +// cy.reload() +// }) - beforeEach(() => { - cy.preserveCookies() - cy.fixture('apiowner').as('apiowner') - cy.fixture('api').as('api') - cy.fixture('common-testdata').as('common-testdata') - cy.visit(login.path) - }) +// beforeEach(() => { +// cy.preserveCookies() +// cy.fixture('apiowner').as('apiowner') +// cy.fixture('api').as('api') +// cy.fixture('common-testdata').as('common-testdata') +// cy.visit(login.path) +// }) - it('authenticates Janis (api owner)', () => { - cy.get('@apiowner').then(({ user }: any) => { - cy.get('@common-testdata').then(({ checkPermission }: any) => { - cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(checkPermission.namespace) - }) - }) - }) +// it('authenticates Janis (api owner)', () => { +// cy.get('@apiowner').then(({ user }: any) => { +// cy.get('@common-testdata').then(({ checkPermission }: any) => { +// cy.login(user.credentials.username, user.credentials.password) +// cy.activateGateway(checkPermission.namespace) +// }) +// }) +// }) - it('Get the authorization token for the service account created with out "Content.Publish" permission', () => { - cy.readFile('cypress/fixtures/state/store.json').then((store_res) => { - let cc = JSON.parse(store_res.credentials) - cy.getAccessToken(cc.clientId, cc.clientSecret).then(() => { - cy.get('@accessTokenResponse').then((token_res: any) => { - token = token_res.body.access_token - }) - }) - }) - }) +// it('Get the authorization token for the service account created with out "Content.Publish" permission', () => { +// cy.readFile('cypress/fixtures/state/store.json').then((store_res) => { +// let cc = JSON.parse(store_res.credentials) +// cy.getAccessToken(cc.clientId, cc.clientSecret).then(() => { +// cy.get('@accessTokenResponse').then((token_res: any) => { +// token = token_res.body.access_token +// }) +// }) +// }) +// }) - it('Prepare the Request Specification for the API', () => { - cy.get('@api').then(({ documentation }: any) => { - cy.setHeaders(documentation.headers) - cy.setAuthorizationToken(token) - cy.setRequestBody(documentation.body) - }) - }) +// it('Prepare the Request Specification for the API', () => { +// cy.get('@api').then(({ documentation }: any) => { +// cy.setHeaders(documentation.headers) +// cy.setAuthorizationToken(token) +// cy.setRequestBody(documentation.body) +// }) +// }) - it('Verify that the document is not published without "Content.Publish" permission', () => { - cy.get('@api').then(({ documentation }: any) => { - cy.makeAPIRequest(documentation.endPoint, 'PUT').then((response:any) => { - expect(response.apiRes.status).to.be.equal(401) - expect(response.apiRes.body.message).contain('Missing authorization scope') - }) - }) - }) +// it('Verify that the document is not published without "Content.Publish" permission', () => { +// cy.get('@api').then(({ documentation }: any) => { +// cy.makeAPIRequest(documentation.endPoint, 'PUT').then((response:any) => { +// expect(response.apiRes.status).to.be.equal(401) +// expect(response.apiRes.body.message).contain('Missing authorization scope') +// }) +// }) +// }) - after(() => { - cy.logout() - cy.clearLocalStorage({ log: true }) - cy.deleteAllCookies() - }) -}) +// after(() => { +// cy.logout() +// cy.clearLocalStorage({ log: true }) +// cy.deleteAllCookies() +// }) +// }) diff --git a/e2e/cypress/tests/12-access-permission/10-identity-provider.cy.ts b/e2e/cypress/tests/12-access-permission/10-identity-provider.cy.ts new file mode 100644 index 000000000..2bf1ff884 --- /dev/null +++ b/e2e/cypress/tests/12-access-permission/10-identity-provider.cy.ts @@ -0,0 +1,49 @@ +import HomePage from '../../pageObjects/home' +import LoginPage from '../../pageObjects/login' +import NameSpacePage from '../../pageObjects/namespace' + +describe('Confirm users can see the proper nav bar items', () => { + const login = new LoginPage() + const home = new HomePage() + const ns = new NameSpacePage() + + before(() => { + cy.visit('/') + cy.deleteAllCookies() + cy.reload() + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + cy.fixture('common-testdata').as('common-testdata') + cy.visit(login.path) + }) + + it('authenticates Janis (api owner) - IDIR', () => { + cy.get('@apiowner').then(({ user }: any) => { + cy.login(user.credentials.username, user.credentials.password) + cy.get(home.apiDirectoryNavButtom).should('be.visible') + cy.get(home.accessNavButtom).should('be.visible') + cy.get(home.applicationsNavButtom).should('be.visible') + cy.get(home.gatewaysNavButtom).should('be.visible') + }) + }) + + it('authenticates Janis (api owner) - GitHub', () => { + cy.get('@apiowner').then(({ githubUser }: any) => { + cy.login(githubUser.credentials.username, githubUser.credentials.password) + cy.get(home.apiDirectoryNavButtom).should('be.visible') + cy.get(home.accessNavButtom).should('be.visible') + cy.get(home.applicationsNavButtom).should('be.visible') + cy.get(home.gatewaysNavButtom).should('not.exist') + }) + }) + + afterEach(() => { + cy.logout() + cy.clearLocalStorage({ log: true }) + cy.deleteAllCookies() + }) + +}) diff --git a/e2e/cypress/tests/13-namespace-preview-mode/01-create-api.cy.ts b/e2e/cypress/tests/13-namespace-preview-mode/01-create-api.cy.ts index 02fea2554..e312a01c0 100644 --- a/e2e/cypress/tests/13-namespace-preview-mode/01-create-api.cy.ts +++ b/e2e/cypress/tests/13-namespace-preview-mode/01-create-api.cy.ts @@ -40,19 +40,18 @@ describe('Create API Spec', () => { }); }) - it('create namespace using gwa cli command', () => { - var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); - cy.exec('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 3000, failOnNonZeroExit: false }).then((response) => { - assert.isNotNaN(response.stdout) - namespace = response.stdout + it('create namespace', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + cy.log('New namespace created: ' + namespace) cy.updateJsonValue('common-testdata.json', 'namespacePreview.namespace', namespace) // cy.updateJsonValue('apiowner.json', 'clientCredentials.clientIdSecret.product.environment.name.config.serviceName', 'cc-service-for-' + namespace) - cy.executeCliCommand("gwa config set --namespace " + namespace) + cy.executeCliCommand("gwa config set --gateway " + namespace) }); }) it('activates new namespace', () => { - home.useNamespace(namespace) + cy.activateGateway(namespace) }) it('creates a new service account', () => { diff --git a/e2e/cypress/tests/13-namespace-preview-mode/02-namespace-preview-mode.cy.ts b/e2e/cypress/tests/13-namespace-preview-mode/02-namespace-preview-mode.cy.ts index 2d73b0753..e026f5aa9 100644 --- a/e2e/cypress/tests/13-namespace-preview-mode/02-namespace-preview-mode.cy.ts +++ b/e2e/cypress/tests/13-namespace-preview-mode/02-namespace-preview-mode.cy.ts @@ -34,7 +34,7 @@ describe('Verify Products when namespace in Preview Mode', () => { cy.get('@common-testdata').then(({ namespacePreview }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.log('Logged in!') - home.useNamespace(namespacePreview.namespace) + cy.activateGateway(namespacePreview.namespace) }) }) }) diff --git a/e2e/cypress/tests/14-org-assignment/01-client-cred-team-access.ts b/e2e/cypress/tests/14-org-assignment/01-client-cred-team-access.ts index 4e8ed934d..9c6c998f9 100644 --- a/e2e/cypress/tests/14-org-assignment/01-client-cred-team-access.ts +++ b/e2e/cypress/tests/14-org-assignment/01-client-cred-team-access.ts @@ -9,11 +9,13 @@ import ServiceAccountsPage from '../../pageObjects/serviceAccounts' import MyAccessPage from '../../pageObjects/myAccess' import ConsumersPage from '../../pageObjects/consumers' let namespace: string +let displayName: string describe('Add Organization to publish API', () => { const login = new LoginPage() const home = new HomePage() const na = new NamespaceAccessPage() + const ns = new NameSpacePage() const pd = new Products() const sa = new ServiceAccountsPage() const apiDir = new ApiDirectoryPage() @@ -48,19 +50,19 @@ describe('Add Organization to publish API', () => { }); }) - it('create namespace using gwa cli command', () => { - var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); - cy.exec('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 3000, failOnNonZeroExit: false }).then((response) => { - assert.isNotNaN(response.stdout) - namespace = response.stdout + it('create namespace', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + displayName = response.displayName + cy.log('New namespace created: ' + namespace) cy.updateJsonValue('common-testdata.json', 'orgAssignment.namespace', namespace) // cy.updateJsonValue('apiowner.json', 'clientCredentials.clientIdSecret.product.environment.name.config.serviceName', 'cc-service-for-' + namespace) - cy.executeCliCommand("gwa config set --namespace " + namespace) + cy.executeCliCommand("gwa config set --gateway " + namespace) }); }) it('activates new namespace', () => { - home.useNamespace(namespace) + cy.activateGateway(namespace) }) it('creates a new service account', () => { @@ -113,6 +115,15 @@ describe('Add Organization to publish API', () => { }) }) + it('Verify My Gateways shows publishing "disabled"', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('contain.text', 'Publishing disabled') + cy.get(ns.listFilterSelect).select('disabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('exist') + cy.get(ns.listFilterSelect).select('enabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('not.exist') + }) + it('Assign organization to the created namespace', () => { cy.visit(apiDir.path) cy.get('@apiowner').then(({ product }: any) => { @@ -120,10 +131,19 @@ describe('Add Organization to publish API', () => { }) }) + it('Verify My Gateways shows publishing "pending"', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('contain.text', 'Pending publishing permission') + cy.get(ns.listFilterSelect).select('pending') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('exist') + cy.get(ns.listFilterSelect).select('enabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('not.exist') + }) + it('Verify Organization Administrator notification banner', () => { cy.visit(apiDir.path) cy.get('@apiowner').then(({ orgAssignment }: any) => { - cy.replaceWord(orgAssignment.orgAdminNotification.parent, 'orgassignment', namespace).then((updatedNotification: string) => { + cy.replaceWord(orgAssignment.orgAdminNotification.parent, 'orgassignment', displayName).then((updatedNotification: string) => { apiDir.checkOrgAdminNotificationBanner(updatedNotification, orgAssignment.orgAdminNotification.child) }) }) @@ -158,11 +178,11 @@ describe('Org Admin approves the request', () => { }) it('Select the namespace', () => { - home.useNamespace(namespace) + cy.activateGateway(namespace) }) - it('Clik on Enable Publishing option from Namespace Page', () => { - cy.visit(ns.path) + it('Click on Enable Publishing option from Namespace Page', () => { + cy.visit(ns.detailPath) cy.wait(2000) cy.contains('a', 'Enable Publishing').click() cy.wait(2000) @@ -181,6 +201,7 @@ describe('Org Admin approves the request', () => { describe('Activate the API to make it visible in API Directory', () => { const login = new LoginPage() const home = new HomePage() + const ns = new NameSpacePage() const pd = new Products() const apiDir = new ApiDirectoryPage() @@ -198,10 +219,19 @@ describe('Activate the API to make it visible in API Directory', () => { it('Authenticates api owner', () => { cy.get('@apiowner').then(({ user }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) + it('Verify My Gateways shows publishing "enabled"', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('contain.text', 'Publishing enabled') + cy.get(ns.listFilterSelect).select('enabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('exist') + cy.get(ns.listFilterSelect).select('disabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('not.exist') + }) + it('update the Dataset in BC Data Catelogue to appear the API in the Directory', () => { cy.visit(pd.path) cy.get('@apiowner').then(({ orgAssignment }: any) => { @@ -301,7 +331,7 @@ describe('Access manager approves developer access request for Kong API ACL auth it('Access Manager logs in', () => { cy.get('@access-manager').then(({ user }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace) + cy.activateGateway(namespace) }) }) diff --git a/e2e/cypress/tests/14-org-assignment/03-verify-org-admin-member-org.ts b/e2e/cypress/tests/14-org-assignment/03-verify-org-admin-member-org.ts index 86fa3c287..23276daf4 100644 --- a/e2e/cypress/tests/14-org-assignment/03-verify-org-admin-member-org.ts +++ b/e2e/cypress/tests/14-org-assignment/03-verify-org-admin-member-org.ts @@ -42,19 +42,18 @@ describe('Multiple Org Adming for the organization', () => { }); }) - it('create namespace using gwa cli command', () => { - var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); - cy.exec('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 3000, failOnNonZeroExit: false }).then((response) => { - assert.isNotNaN(response.stdout) - namespace = response.stdout + it('create namespace', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + cy.log('New namespace created: ' + namespace) cy.updateJsonValue('common-testdata.json', 'orgAssignment.namespace', namespace) // cy.updateJsonValue('apiowner.json', 'clientCredentials.clientIdSecret.product.environment.name.config.serviceName', 'cc-service-for-' + namespace) - cy.executeCliCommand("gwa config set --namespace " + namespace) + cy.executeCliCommand("gwa config set --gateway " + namespace) }); }) it('activates new namespace', () => { - home.useNamespace(namespace) + cy.activateGateway(namespace) }) diff --git a/e2e/cypress/tests/14-org-assignment/05-verify-org-admin-member-org-unit.ts b/e2e/cypress/tests/14-org-assignment/05-verify-org-admin-member-org-unit.ts index 0fe81f42a..8c0be9735 100644 --- a/e2e/cypress/tests/14-org-assignment/05-verify-org-admin-member-org-unit.ts +++ b/e2e/cypress/tests/14-org-assignment/05-verify-org-admin-member-org-unit.ts @@ -41,19 +41,18 @@ describe('Multiple Org Admin for the organization', () => { }); }) - it('create namespace using gwa cli command', () => { - var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); - cy.exec('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 3000, failOnNonZeroExit: false }).then((response) => { - assert.isNotNaN(response.stdout) - namespace = response.stdout + it('create namespace', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + cy.log('New namespace created: ' + namespace) cy.updateJsonValue('common-testdata.json', 'orgAssignment.namespace', namespace) // cy.updateJsonValue('apiowner.json', 'clientCredentials.clientIdSecret.product.environment.name.config.serviceName', 'cc-service-for-' + namespace) - cy.executeCliCommand("gwa config set --namespace " + namespace) + cy.executeCliCommand("gwa config set --gateway " + namespace) }); }) it('activates new namespace', () => { - home.useNamespace(namespace) + cy.activateGateway(namespace) }) it('creates a new service account', () => { diff --git a/e2e/cypress/tests/15-aps-api/01-create-api.cy.ts b/e2e/cypress/tests/15-aps-api/01-create-api.cy.ts index 7dd135961..f479aea8d 100644 --- a/e2e/cypress/tests/15-aps-api/01-create-api.cy.ts +++ b/e2e/cypress/tests/15-aps-api/01-create-api.cy.ts @@ -21,7 +21,7 @@ describe('Create API Spec', () => { beforeEach(() => { cy.preserveCookies() cy.fixture('apiowner').as('apiowner') - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('common-testdata').as('common-testdata') cy.visit(login.path) }) @@ -34,26 +34,17 @@ describe('Create API Spec', () => { }) }) - it('Set token with gwa config command', () => { - cy.exec('gwa config set --token ' + userSession, { timeout: 3000, failOnNonZeroExit: false }).then((response) => { - expect(response.stdout).to.contain("Config settings saved") - }); - }) - - it('create namespace using gwa cli command', () => { - var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); - cy.exec('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 3000, failOnNonZeroExit: false }).then((response) => { - assert.isNotNaN(response.stdout) - namespace = response.stdout + it('create namespace', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + cy.log('New namespace created: ' + namespace) cy.updateJsonValue('common-testdata.json', 'apiTest.namespace', namespace) - cy.updateJsonValue('api.json', 'organization.expectedNamespace.name', namespace) - // cy.updateJsonValue('apiowner.json', 'clientCredentials.clientIdSecret.product.environment.name.config.serviceName', 'cc-service-for-' + namespace) - cy.executeCliCommand("gwa config set --namespace " + namespace) + cy.updateJsonValue('api-v2.json', 'organization.expectedNamespace.name', namespace) }); }) it('activates new namespace', () => { - home.useNamespace(namespace) + cy.activateGateway(namespace) }) it('Associate Namespace to the organization Unit', () => { diff --git a/e2e/cypress/tests/15-aps-api/02-organization.cy.ts b/e2e/cypress/tests/15-aps-api/02-organization.cy.ts index 989a099f5..48f115569 100644 --- a/e2e/cypress/tests/15-aps-api/02-organization.cy.ts +++ b/e2e/cypress/tests/15-aps-api/02-organization.cy.ts @@ -35,7 +35,7 @@ describe('API Tests to verify the Organization details in the response', () => { beforeEach(() => { cy.preserveCookies() - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.request("ds/api/v2/organizations") }) @@ -62,7 +62,7 @@ describe('Verify /Organization/{Org} end point', () => { beforeEach(() => { cy.preserveCookies() - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.request("ds/api/v2/organizations") }) @@ -77,7 +77,12 @@ describe('Verify /Organization/{Org} end point', () => { cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName, 'GET').then((response:any) => { expect(response.apiRes.status).to.be.equal(200) cy.addToAstraScanIdList(response.astraRes.body.status) - assert.isTrue(Cypress._.isEqual(response.apiRes.body.orgUnits[0], organization.orgExpectedList)) + assert.isTrue( + response.apiRes.body.orgUnits.some((orgUnit: any) => + Cypress._.isEqual(orgUnit, organization.orgExpectedList) + ), + 'Expected org unit not found in orgUnits' + ); }) }) }) @@ -99,7 +104,7 @@ describe('Get the Organization Role', () => { var expectedResponse: any = {} beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -151,7 +156,7 @@ describe('Get the Namespace associated with the organization', () => { var expectedResponse: any = {} beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -175,7 +180,6 @@ describe('Get the Namespace associated with the organization', () => { it('Compare the Namespace values in response against the expected values', () => { cy.get('@api').then(({ organization }: any) => { expectedResponse = organization.expectedNamespace - // assert.isTrue(Cypress._.isEqual(response, expectedResponse)) cy.compareJSONObjects(response, expectedResponse, true) }) }) @@ -189,7 +193,7 @@ describe('Delete the Namespace associated with the organization', () => { var expectedResponse: any = {} beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('apiowner').as('apiowner') cy.fixture('common-testdata').as('common-testdata') }) @@ -232,7 +236,7 @@ describe('Add and Get Organization Access', () => { var expectedResponse: any = {} beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('apiowner').as('apiowner') }) diff --git a/e2e/cypress/tests/15-aps-api/03-documentation.cy.ts b/e2e/cypress/tests/15-aps-api/03-documentation.cy.ts index 08b39b41d..3dd8c5a86 100644 --- a/e2e/cypress/tests/15-aps-api/03-documentation.cy.ts +++ b/e2e/cypress/tests/15-aps-api/03-documentation.cy.ts @@ -38,7 +38,7 @@ describe('API Tests for Updating documentation', () => { const home = new HomePage() beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -69,7 +69,7 @@ describe('API Tests for Fetching documentation', () => { var response: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -103,7 +103,7 @@ describe('API Tests for Deleting documentation', () => { const home = new HomePage() beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -137,7 +137,7 @@ describe('API Tests to verify no value in Get call after deleting document conte const home = new HomePage() beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -164,7 +164,7 @@ describe('API Tests to verify Get documentation content', () => { let slugID: string beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { diff --git a/e2e/cypress/tests/15-aps-api/05-authorizationProfiles.cy.ts b/e2e/cypress/tests/15-aps-api/05-authorizationProfiles.cy.ts index c490f0ca1..107373d66 100644 --- a/e2e/cypress/tests/15-aps-api/05-authorizationProfiles.cy.ts +++ b/e2e/cypress/tests/15-aps-api/05-authorizationProfiles.cy.ts @@ -49,7 +49,7 @@ testData.forEach((testCase: any) => { var expectedResponse: any = {} beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -113,7 +113,7 @@ describe('API Tests for Authorization Profiles created with inheritFrom attribut let expectedResponse: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('apiowner').as('apiowner') cy.fixture('common-testdata').as('common-testdata') }) @@ -180,7 +180,7 @@ describe('Published a shared authorization profile', () => { let expectedResponse: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('apiowner').as('apiowner') cy.fixture('common-testdata').as('common-testdata') }) @@ -233,7 +233,7 @@ describe('Deleted shared auth profile', () => { beforeEach(() => { cy.preserveCookies() cy.fixture('apiowner').as('apiowner') - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('common-testdata').as('common-testdata') }) @@ -242,7 +242,7 @@ describe('Deleted shared auth profile', () => { cy.getUserSessionTokenValue(apiTest.namespace).then((value) => { userSession = value namespace = apiTest.namespace - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) @@ -285,7 +285,7 @@ describe('Verify that client ID of deleted shared auth profile in IDP', () => { cy.fixture('apiowner').as('apiowner') cy.fixture('state/regen').as('regen') cy.fixture('admin').as('admin') - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Authenticates Admin owner', () => { diff --git a/e2e/cypress/tests/15-aps-api/06-products.cy.ts b/e2e/cypress/tests/15-aps-api/06-products.cy.ts index e169e786e..f05f4a305 100644 --- a/e2e/cypress/tests/15-aps-api/06-products.cy.ts +++ b/e2e/cypress/tests/15-aps-api/06-products.cy.ts @@ -26,11 +26,11 @@ describe('Get the user session token to check ', () => { }) it('authenticates Janis (api owner) to get the user session token', () => { - cy.getUserSession().then(() => { + cy.interceptUserSession().then(() => { cy.get('@apiowner').then(({ user }: any) => { cy.get('@common-testdata').then(({ apiTest }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(apiTest.namespace) + cy.activateGateway(apiTest.namespace) namespace = apiTest.namespace cy.get('@login').then(function (xhr: any) { userSession = xhr.response.headers['x-auth-request-access-token'] @@ -49,7 +49,7 @@ describe('API Tests for Updating Products', () => { var response: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -104,7 +104,7 @@ describe('Verify that created Product is displayed in UI', () => { beforeEach(() => { cy.preserveCookies() cy.fixture('apiowner').as('apiowner') - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('common-testdata').as('common-testdata') cy.visit(login.path) }) @@ -124,7 +124,7 @@ describe('API Tests for Delete Products', () => { var response: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { diff --git a/e2e/cypress/tests/15-aps-api/07-api-directory.cy.ts b/e2e/cypress/tests/15-aps-api/07-api-directory.cy.ts index 7f235b2f7..273240dfa 100644 --- a/e2e/cypress/tests/15-aps-api/07-api-directory.cy.ts +++ b/e2e/cypress/tests/15-aps-api/07-api-directory.cy.ts @@ -40,7 +40,7 @@ describe('API Tests for Updating dataset', () => { let directoryName: string beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('apiowner').as('apiowner') cy.fixture('common-testdata').as('common-testdata') }) diff --git a/e2e/cypress/tests/15-aps-api/08-namespaces.cy.ts b/e2e/cypress/tests/15-aps-api/08-namespaces.cy.ts index 433f5fa86..afaa14800 100644 --- a/e2e/cypress/tests/15-aps-api/08-namespaces.cy.ts +++ b/e2e/cypress/tests/15-aps-api/08-namespaces.cy.ts @@ -39,7 +39,7 @@ describe('API Tests for Namespace Report', () => { var response: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -65,7 +65,7 @@ describe('API Tests for Namespace List', () => { var response: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -97,7 +97,7 @@ describe('API Tests for Namespace Activities', () => { var response: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') }) it('Prepare the Request Specification for the API', () => { @@ -124,7 +124,7 @@ describe('API Tests for Namespace Summary', () => { var response: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('apiowner').as('apiowner') cy.fixture('common-testdata').as('common-testdata') }) @@ -162,7 +162,7 @@ describe('API Tests for Create Namespace', () => { var response: any beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('apiowner').as('apiowner') }) @@ -178,7 +178,7 @@ describe('API Tests for Create Namespace', () => { cy.makeAPIRequest(namespaces.endPoint, 'POST').then((res:any) => { expect(res.apiRes.status).to.be.equal(200) cy.addToAstraScanIdList(res.astraRes.body.status) - expect(res.apiRes.body.displayName).to.be.equal(null) + expect(res.apiRes.body.displayName).to.be.equal("janis's Gateway") nameSpace = res.apiRes.body.name }) }) @@ -220,7 +220,7 @@ describe('API Tests for Create Namespace', () => { describe('API Tests for invalid namespace name', () => { beforeEach(() => { - cy.fixture('api').as('api') + cy.fixture('api-v2').as('api') cy.fixture('apiowner').as('apiowner') }) @@ -253,7 +253,7 @@ describe('API Tests for invalid namespace name', () => { // beforeEach(() => { -// cy.fixture('api').as('api') +// cy.fixture('api-v2').as('api') // cy.fixture('apiowner').as('apiowner') // }) diff --git a/e2e/cypress/tests/16-gwa-cli/01-cli-commands.ts b/e2e/cypress/tests/16-gwa-cli/01-cli-commands.ts index be6fbe98d..0b9b3d869 100644 --- a/e2e/cypress/tests/16-gwa-cli/01-cli-commands.ts +++ b/e2e/cypress/tests/16-gwa-cli/01-cli-commands.ts @@ -1,22 +1,11 @@ -import LoginPage from '../../pageObjects/login' -import ApplicationPage from '../../pageObjects/applications' -import ApiDirectoryPage from '../../pageObjects/apiDirectory' -import MyAccessPage from '../../pageObjects/myAccess' -const YAML = require('yamljs'); -let userSession: any let cli = require("../../fixtures/test_data/gwa-cli.json") +let userSession: any var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); -const jose = require('node-jose') -describe('Verify CLI commands', () => { - const login = new LoginPage() - const apiDir = new ApiDirectoryPage() - const app = new ApplicationPage() - const ma = new MyAccessPage() +describe('Verify config / login CLI commands', () => { let namespace: string before(() => { - // cy.visit('/') cy.deleteAllCookies() cy.reload(true) }) @@ -25,7 +14,6 @@ describe('Verify CLI commands', () => { cy.preserveCookies() cy.fixture('apiowner').as('apiowner') cy.fixture('common-testdata').as('common-testdata') - // cy.visit(login.path) }) it('authenticates Janis (api owner) to get the user session token', () => { @@ -77,15 +65,21 @@ describe('Verify CLI commands', () => { }) it('Check gwa command to create namespace', () => { - cy.executeCliCommand('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http').then((response) => { + cy.executeCliCommand('gwa gateway create --generate --host ' + cleanedUrl + ' --scheme http').then((response) => { assert.isNotNaN(response.stdout) - namespace = response.stdout + // Use regex to extract the gateway ID + const match = response.stdout.match(/Gateway ID: ([\w-]+)/); + if (match && match[1]) { + namespace = match[1]; + assert.isNotNull(namespace); + } else { + throw new Error('Failed to extract Gateway ID from response: ' + response.stdout); + } }); }) - - it('Check gwa namespace list command and verify the created namespace in the list', () => { - cy.executeCliCommand('gwa namespace list --host ' + cleanedUrl + ' --scheme http').then((response) => { + it('Check gwa gateway list command and verify the created namespace in the list', () => { + cy.executeCliCommand('gwa gateway list --host ' + cleanedUrl + ' --scheme http').then((response) => { expect(response.stdout).to.contain(namespace); }); }) @@ -96,4 +90,4 @@ describe('Verify CLI commands', () => { cy.deleteAllCookies() }) -}) \ No newline at end of file +}) diff --git a/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config-quick-start.ts b/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config-quick-start.ts new file mode 100644 index 000000000..403087a01 --- /dev/null +++ b/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config-quick-start.ts @@ -0,0 +1,115 @@ +import LoginPage from '../../pageObjects/login' +import ApplicationPage from '../../pageObjects/applications' +import ApiDirectoryPage from '../../pageObjects/apiDirectory' +import MyAccessPage from '../../pageObjects/myAccess' +import HomePage from '../../pageObjects/home'; +import Products from '../../pageObjects/products'; +const YAML = require('yamljs'); +let cli = require("../../fixtures/test_data/gwa-cli.json") +const { v4: uuidv4 } = require('uuid') +const jose = require('node-jose') + +let userSession: any +const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) +const serviceName = 'my-service-' + customId + +describe('Verify CLI commands for generate/apply config', () => { + const login = new LoginPage() + const apiDir = new ApiDirectoryPage() + const app = new ApplicationPage() + const ma = new MyAccessPage() + const pd = new Products() + let namespace: string + const home = new HomePage() + + before(() => { + // cy.visit('/') + cy.reload(true) + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + // cy.visit(login.path) + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.get('@apiowner').then(({ apiTest }: any) => { + cy.getUserSessionTokenValue(apiTest.namespace, false).then((value) => { + userSession = value + }) + }) + }) + + it('Check gwa config command to set token', () => { + cy.executeCliCommand('gwa config set --token ' + userSession).then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('Check gwa command to generate config for client credential template', () => { + const command = [ + 'gwa generate-config --template quick-start', + `--service ${serviceName}`, + '--upstream https://httpbin.org', + '--org ministry-of-health', + '--org-unit planning-and-innovation-division', + '--out gw-config-qs.yaml' + ].join(' '); + cy.executeCliCommand(command).then((response) => { + expect(response.stdout).to.contain("File gw-config-qs.yaml created") + }); + }) + + it('Check gwa command to apply generated config', () => { + cy.executeCliCommand('gwa apply -i gw-config-qs.yaml').then((response) => { + expect(response.stdout).to.contain("3/3 Published, 0 Skipped") + let wordOccurrences = (response.stdout.match(/\bcreated\b/g) || []).length; + expect(wordOccurrences).to.equal(2) + }); + }) + + it('Check gwa status --hosts include routes', () => { + cy.executeCliCommand('gwa status --hosts').then((response) => { + expect(response.stdout).to.contain('https://' + serviceName + '.dev.api.gov.bc.ca') + }); + }) + + it('activates namespace in Portal', () => { + cy.executeCliCommand('gwa gateway current').then((response) => { + const namespace = response.stdout.match(/\bgw-\w+/g)[0] + cy.activateGateway(namespace) + }) + }) + + it('Verify that the product created through gwa command is displayed in the portal', () => { + cy.visit(pd.path) + pd.editProductEnvironment(serviceName + ' API', 'dev') + }) + + it('Verify that the dataset created through GWA comand is assocuated with the product', () => { + cy.visit(pd.path) + pd.verifyDataset(serviceName, serviceName + ' API') + }) + + it('Verify service name validation error in new namespace', () => { + const command = [ + 'gwa generate-config --template quick-start', + `--service ${serviceName}`, + '--upstream https://httpbin.org', + '--org ministry-of-health', + '--org-unit planning-and-innovation-division' + ].join(' '); + cy.executeCliCommand('gwa gateway create --generate').then((response) => { + const namespace = response.stdout.match(/\bgw-\w+/g)[0] + cy.executeCliCommand(command).then((response) => { + expect(response.stderr).to.contain(`Error: Service ${serviceName} is already in use. Suggestion: ${namespace}-${serviceName}`) + }); + }); + }) + + after(() => { + cy.logout() +}) + +}) \ No newline at end of file diff --git a/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts b/e2e/cypress/tests/16-gwa-cli/03-cli-generate-config-client-cred.ts similarity index 68% rename from e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts rename to e2e/cypress/tests/16-gwa-cli/03-cli-generate-config-client-cred.ts index e4214a9ef..fa0e28413 100644 --- a/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts +++ b/e2e/cypress/tests/16-gwa-cli/03-cli-generate-config-client-cred.ts @@ -5,18 +5,21 @@ import MyAccessPage from '../../pageObjects/myAccess' import HomePage from '../../pageObjects/home'; import Products from '../../pageObjects/products'; const YAML = require('yamljs'); -let userSession: any let cli = require("../../fixtures/test_data/gwa-cli.json") - +const { v4: uuidv4 } = require('uuid') const jose = require('node-jose') +let userSession: any +let namespace: string +const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) +const serviceName = 'my-service-' + customId + describe('Verify CLI commands for generate/apply config', () => { const login = new LoginPage() const apiDir = new ApiDirectoryPage() const app = new ApplicationPage() const ma = new MyAccessPage() const pd = new Products() - let namespace: string const home = new HomePage() before(() => { @@ -45,26 +48,38 @@ describe('Verify CLI commands for generate/apply config', () => { }) it('Check gwa command to generate config for client credential template', () => { - cy.executeCliCommand('gwa generate-config --template client-credentials-shared-idp --service my-service --upstream https://httpbin.org --org ministry-of-health --org-unit planning-and-innovation-division').then((response) => { - expect(response.stdout).to.contain("File gw-config.yml created") + const serviceName = 'my-service-' + customId + const command = [ + 'gwa generate-config --template client-credentials-shared-idp', + `--service ${serviceName}`, + '--upstream https://httpbin.org', + '--org ministry-of-health', + '--org-unit planning-and-innovation-division', + '--out gw-config-cc.yaml' + ].join(' '); + cy.executeCliCommand(command).then((response) => { + expect(response.stdout).to.contain("File gw-config-cc.yaml created") }); }) it('Check gwa command to apply generated config', () => { - cy.executeCliCommand('gwa apply -i gw-config.yml').then((response) => { + cy.executeCliCommand('gwa apply -i gw-config-cc.yaml').then((response) => { + expect(response.stdout).to.contain("4/4 Published, 0 Skipped") let wordOccurrences = (response.stdout.match(/\bcreated\b/g) || []).length; expect(wordOccurrences).to.equal(3) - namespace = response.stdout.match(/\bgw-\w+/g)[0] }); }) - it('activates new namespace', () => { - home.useNamespace(namespace) + it('activates namespace in Portal', () => { + cy.executeCliCommand('gwa gateway current').then((response) => { + namespace = response.stdout.match(/\bgw-\w+/g)[0] + cy.activateGateway(namespace) + }) }) it('Verify that the product created through gwa command is displayed in the portal', () => { cy.visit(pd.path) - pd.editProductEnvironment('my-service API', 'dev') + pd.editProductEnvironment(serviceName + ' API', 'dev') }) it('Verify the Authorization scope and issuer details for the product', () => { @@ -77,11 +92,7 @@ describe('Verify CLI commands for generate/apply config', () => { it('Verify that the dataset created through GWA comand is assocuated with the product', () => { cy.visit(pd.path) - pd.verifyDataset('my-service', 'my-service API') - }) - - it('Navigate to home path', () => { - cy.visit(login.path) + pd.verifyDataset(serviceName, serviceName + ' API') }) after(() => { diff --git a/e2e/cypress/tests/16-gwa-cli/04-cli-gateway-create.ts b/e2e/cypress/tests/16-gwa-cli/04-cli-gateway-create.ts new file mode 100644 index 000000000..f23b355c6 --- /dev/null +++ b/e2e/cypress/tests/16-gwa-cli/04-cli-gateway-create.ts @@ -0,0 +1,131 @@ +import NameSpacePage from '../../pageObjects/namespace' + +const { v4: uuidv4 } = require('uuid') +let cli = require("../../fixtures/test_data/gwa-cli.json") + +let userSession: any +let gatewayId: string +let displayName: string +let customId: string + +var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); + +describe('Verify "gateway create" and "gateway list" commands', () => { + const ns = new NameSpacePage() + customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) + + before(() => { + cy.deleteAllCookies() + cy.reload(true) + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + cy.fixture('common-testdata').as('common-testdata') + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.getUserSessionTokenValue('', false).then((value) => { + userSession = value + }) + }) + + it('Set token with gwa config command', () => { + cy.exec('gwa config set --token ' + userSession, { timeout: 3000, failOnNonZeroExit: false }).then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('create gateway - id and display name provided', () => { + cy.get('@common-testdata').then(({ myGateways }: any) => { + gatewayId = myGateways["namespace1"].gatewayId + '-' + customId + displayName = myGateways["namespace1"].displayName + cy.executeCliCommand('gwa gateway create --gateway-id ' + gatewayId + ' --display-name ' + displayName + ' --host ' + cleanedUrl + ' --scheme http').then((response) => { + assert.isNotNaN(response.stdout) + const gatewayIdMatch = response.stdout.match(/Gateway ID: ([\w-]+)/); + if (gatewayIdMatch && gatewayIdMatch[1]) { + const gatewayIdResult = gatewayIdMatch[1]; + assert.equal(gatewayIdResult, gatewayId); + } else { + throw new Error('Failed to extract Gateway ID from response: ' + response.stdout); + } + const displayNameMatch = response.stdout.match(/display name: ([\w-]+)/); + if (displayNameMatch && displayNameMatch[1]) { + const displayNameResult = displayNameMatch[1]; + assert.equal(displayNameResult, displayName); + } else { + throw new Error('Failed to extract Display Name from response: ' + response.stdout); + } + }); + // Verify the created gateway in Portal + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${gatewayId}"]`) + .should('contain.text', gatewayId) + .should('contain.text', displayName) + }); + }); + + it('verify gateway list command', () => { + cy.executeCliCommand('gwa gateway list').then((response) => { + assert.isNotNaN(response.stdout) + assert.isTrue(response.stdout.includes(gatewayId), `The output should include the gateway ID: ${gatewayId}`) + assert.isTrue(response.stdout.includes(displayName), `The output should include the display name: ${displayName}`) + }); + cy.deleteGatewayCli(gatewayId, false) + }); + + it('create gateway - id provided', () => { + cy.get('@common-testdata').then(({ myGateways }: any) => { + gatewayId = myGateways["namespace2"].gatewayId + '-' + customId + displayName = "janis's Gateway" + cy.executeCliCommand('gwa gateway create --gateway-id ' + gatewayId + ' --host ' + cleanedUrl + ' --scheme http').then((response) => { + assert.isNotNaN(response.stdout) + const gatewayIdMatch = response.stdout.match(/Gateway ID: ([\w-]+)/); + if (gatewayIdMatch && gatewayIdMatch[1]) { + const gatewayIdResult = gatewayIdMatch[1]; + assert.equal(gatewayIdResult, gatewayId); + } else { + throw new Error('Failed to extract Gateway ID from response: ' + response.stdout); + } + }); + // Verify the created gateway + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${gatewayId}"]`) + .should('contain.text', gatewayId) + .should('contain.text', displayName) + cy.deleteGatewayCli(gatewayId, false) + }); + }); + + it('create gateway - display name provided', () => { + cy.get('@common-testdata').then(({ myGateways }: any) => { + let generatedGatewayId: string + displayName = myGateways["namespace3"].displayName + cy.executeCliCommand('gwa gateway create --display-name ' + displayName + ' --host ' + cleanedUrl + ' --scheme http').then((response) => { + assert.isNotNaN(response.stdout) + const gatewayIdMatch = response.stdout.match(/Gateway ID: ([\w-]+)/); + generatedGatewayId = gatewayIdMatch[1]; + const displayNameMatch = response.stdout.match(/display name: ([\w-]+)/); + if (displayNameMatch && displayNameMatch[1]) { + const displayNameResult = displayNameMatch[1]; + assert.equal(displayNameResult, displayName); + } else { + throw new Error('Failed to extract Display Name from response: ' + response.stdout); + } + // Verify the created gateway + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${generatedGatewayId}"]`) + .should('contain.text', generatedGatewayId) + .should('contain.text', displayName) + cy.deleteGatewayCli(generatedGatewayId, false) + }); + }); + }); + + after(() => { + cy.logout() + cy.clearLocalStorage({ log: true }) + cy.deleteAllCookies() + }) +}) diff --git a/e2e/cypress/tests/17-delete-application/03-delete-application-with-approved-request.cy.ts b/e2e/cypress/tests/17-delete-application/03-delete-application-with-approved-request.cy.ts index 008364333..c07b38e2b 100644 --- a/e2e/cypress/tests/17-delete-application/03-delete-application-with-approved-request.cy.ts +++ b/e2e/cypress/tests/17-delete-application/03-delete-application-with-approved-request.cy.ts @@ -76,7 +76,7 @@ describe('Approve Pending Request Spec', () => { cy.get('@access-manager').then(({ user }: any) => { cy.get('@common-testdata').then(({ namespace }: any) => { cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(namespace); + cy.activateGateway(namespace); }) }) }) diff --git a/e2e/cypress/tests/17-delete-application/04-delete-namespace-gwa.ts b/e2e/cypress/tests/17-delete-application/04-delete-namespace-gwa.ts index 87768c2f6..143461f4e 100644 --- a/e2e/cypress/tests/17-delete-application/04-delete-namespace-gwa.ts +++ b/e2e/cypress/tests/17-delete-application/04-delete-namespace-gwa.ts @@ -34,30 +34,37 @@ describe('Verify namespace delete using gwa command', () => { it('Create namespace using gwa cli command', () => { var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); - cy.exec('gwa namespace create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 3000, failOnNonZeroExit: false }).then((response) => { + cy.exec('gwa gateway create --generate --host ' + cleanedUrl + ' --scheme http', { timeout: 3000, failOnNonZeroExit: false }).then((response) => { assert.isNotNaN(response.stdout) - _namespace = response.stdout + // Use regex to extract the gateway ID + const match = response.stdout.match(/Gateway ID: ([\w-]+)/); + if (match && match[1]) { + _namespace = match[1]; + assert.isNotNull(_namespace); + } else { + throw new Error('Failed to extract Gateway ID from response: ' + response.stdout); + } }); - }) + }); - it('Check gwa namespace destroy command for soft deleting namespace', () => { - cy.executeCliCommand('gwa namespace destroy ' + _namespace).then((response) => { - expect(response.stdout).to.contain('Namespace destroyed: ' + _namespace); + it('Check gwa gateway destroy command for soft deleting namespace', () => { + cy.executeCliCommand('gwa gateway destroy ' + _namespace).then((response) => { + expect(response.stdout).to.contain('Gateway destroyed: ' + _namespace); }); }) - it('Check that deleted namespace does not display in gwa namespace list command', () => { - cy.executeCliCommand('gwa namespace list').then((response) => { + it('Check that deleted namespace does not display in gwa gateway list command', () => { + cy.executeCliCommand('gwa gateway list').then((response) => { expect(response.stdout).not.to.contain(_namespace); }); }) - it('Check gwa namespace destroy command for the namespace associated with services', () => { + it('Check gwa gateway destroy command for the namespace associated with services', () => { cy.get('@common-testdata').then(({ namespace }: any) => { _namespace = namespace - cy.executeCliCommand('gwa config set --namespace ' + namespace).then((response) => { + cy.executeCliCommand('gwa config set --gateway ' + namespace).then((response) => { expect(response.stdout).to.contain("Config settings saved") - cy.executeCliCommand('gwa namespace destroy').then((response) => { + cy.executeCliCommand('gwa gateway destroy').then((response) => { expect(response.stderr).to.contain('Error: Validation Failed'); }); }) @@ -65,7 +72,7 @@ describe('Verify namespace delete using gwa command', () => { }) it('Check validation if any consumer is associated with namespace for hard deleting the namespace', () => { - cy.executeCliCommand('gwa namespace destroy --force').then((response) => { + cy.executeCliCommand('gwa gateway destroy --force').then((response) => { expect(response.stderr).to.contain('Error: Validation Failed'); }); }) diff --git a/e2e/cypress/tests/19-api-v3/01-api-directory.ts b/e2e/cypress/tests/19-api-v3/01-api-directory.ts new file mode 100644 index 000000000..6ad03ef39 --- /dev/null +++ b/e2e/cypress/tests/19-api-v3/01-api-directory.ts @@ -0,0 +1,278 @@ +describe('API Directory', () => { + let workingData: any + + before(() => { + cy.buildOrgGatewayDatasetAndProduct().then((data) => { + workingData = data + }) + }) + + describe('API Directory (Public)', () => { + it('PUT /organizations/{org}/datasets (resources/contacts)', () => { + const { org, gateway, dataset, datasetId, product } = workingData + + const payload = { + name: `org-dataset-${datasetId}-res`, + license_title: 'Open Government Licence - British Columbia', + security_class: 'PUBLIC', + view_audience: 'Public', + download_audience: 'Public', + record_publish_date: '2017-09-05', + notes: 'Some notes', + title: 'A title about my dataset', + tags: ['tag1', 'tag2'], + organization: org.name, + organizationUnit: org.orgUnits[0].name, + resources: [ + { + name: 'API Doc', + url: 'https://raw.githubusercontent.com/bcgov/api-specs/master/bcdc/bcdc.json', + format: 'openapi-json', + }, + ], + contacts: [ + { + name: 'Joe Smith', + email: 'joe@gov.bc.ca', + role: 'pointOfContact', + }, + ], + } + cy.setRequestBody(payload) + cy.callAPI(`ds/api/v3/organizations/${org.name}/datasets`, 'PUT').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + cy.callAPI( + `ds/api/v3/organizations/${org.name}/datasets/${payload.name}`, + 'GET' + ).then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + const match = { + name: payload.name, + license_title: 'Open Government Licence - British Columbia', + security_class: 'PUBLIC', + view_audience: 'Public', + download_audience: 'Public', + record_publish_date: '2017-09-05', + notes: 'Some notes', + title: 'A title about my dataset', + isInCatalog: false, + isDraft: true, + contacts: [ + { + role: 'pointOfContact', + name: 'Joe Smith', + email: 'joe@gov.bc.ca', + }, + ], + resources: [ + { + name: 'API Doc', + url: 'https://raw.githubusercontent.com/bcgov/api-specs/master/bcdc/bcdc.json', + format: 'openapi-json', + }, + ], + tags: ['tag1', 'tag2'], + organization: { + name: org.name, + title: 'Some good title about kittens', + tags: [], + description: 'Some good description about kittens', + }, + organizationUnit: { + name: org.orgUnits[0].name, + title: 'Division of fun toys to play', + tags: [], + description: 'Some good description about how we manage our toys', + }, + } + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + expect(JSON.stringify(body.contacts)).to.be.equal( + JSON.stringify(match.contacts) + ) + expect(JSON.stringify(body.resources)).to.be.equal( + JSON.stringify(match.resources) + ) + }) + } + ) + }) + + it('GET /directory', () => { + cy.callAPI('ds/api/v3/directory', 'GET').then( + ({ apiRes: { status, body } }: any) => { + cy.log(`Directory ${JSON.stringify(body, null, 4)}`) + expect(status).to.be.equal(200) + expect(body.length).to.be.greaterThan(0) + } + ) + }) + + it('GET /directory/{datasetId}', () => { + const { org, gateway, dataset, datasetId, product } = workingData + cy.callAPI(`ds/api/v3/directory/${datasetId}`, 'GET').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + const match = { + name: dataset.name, + title: 'A title about my dataset', + notes: 'Some notes', + license_title: 'Open Government Licence - British Columbia', + security_class: 'PUBLIC', + view_audience: 'Public', + tags: ['tag1', 'tag2'], + record_publish_date: '2017-09-05', + isInCatalog: false, + organization: { + name: org.name, + title: 'Some good title about kittens', + }, + organizationUnit: { + name: org.orgUnits[0].name, + title: 'Division of fun toys to play', + }, + products: [ + { + name: `my-product-on-${gateway.gatewayId}`, + environments: [ + { name: 'dev', active: true, flow: 'public', services: [] }, + ], + }, + ], + } + delete body.products[0].id + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + } + ) + }) + }) + + describe('API Directory (Gateway Management)', () => { + it('GET /gateways/{gatewayId}/datasets/{name}', () => { + const { org, gateway, dataset, datasetId, product } = workingData + cy.callAPI( + `ds/api/v3/gateways/${gateway.gatewayId}/datasets/${dataset.name}`, + 'GET' + ).then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + const match = { + name: dataset.name, + license_title: 'Open Government Licence - British Columbia', + security_class: 'PUBLIC', + view_audience: 'Public', + download_audience: 'Public', + record_publish_date: '2017-09-05', + notes: 'Some notes', + title: 'A title about my dataset', + isInCatalog: false, + isDraft: true, + contacts: [], + resources: [], + tags: ['tag1', 'tag2'], + organization: { + name: org.name, + title: 'Some good title about kittens', + tags: [], + description: 'Some good description about kittens', + }, + organizationUnit: { + name: org.orgUnits[0].name, + title: 'Division of fun toys to play', + tags: [], + description: 'Some good description about how we manage our toys', + }, + } + + delete body.id + + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + }) + }) + + it('GET /gateways/{gatewayId}/directory', () => { + const { org, gateway, dataset, datasetId, product } = workingData + cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}/directory`, 'GET').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + const match = [ + { + name: dataset.name, + title: 'A title about my dataset', + notes: 'Some notes', + license_title: 'Open Government Licence - British Columbia', + view_audience: 'Public', + security_class: 'PUBLIC', + record_publish_date: '2017-09-05', + tags: ['tag1', 'tag2'], + organization: { + name: org.name, + title: 'Some good title about kittens', + }, + organizationUnit: { + name: org.orgUnits[0].name, + title: 'Division of fun toys to play', + }, + products: [ + { + name: product.name, + environments: [{ name: 'dev', active: true, flow: 'public' }], + }, + ], + }, + ] + + delete body[0].products[0].id + delete body[0].id + + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + } + ) + }) + + it('GET /gateways/{gatewayId}/directory/{id}', () => { + const { org, gateway, dataset, datasetId, product } = workingData + cy.callAPI( + `ds/api/v3/gateways/${gateway.gatewayId}/directory/${datasetId}`, + 'GET' + ).then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + const match = { + name: dataset.name, + title: 'A title about my dataset', + notes: 'Some notes', + license_title: 'Open Government Licence - British Columbia', + security_class: 'PUBLIC', + view_audience: 'Public', + tags: ['tag1', 'tag2'], + record_publish_date: '2017-09-05', + isInCatalog: false, + organization: { + name: org.name, + title: 'Some good title about kittens', + }, + organizationUnit: { + name: org.orgUnits[0].name, + title: 'Division of fun toys to play', + }, + products: [ + { + name: product.name, + environments: [{ name: 'dev', active: true, flow: 'public', services: [] }], + }, + ], + } + + delete body.products[0].id + delete body.id + + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + }) + }) + }) +}) diff --git a/e2e/cypress/tests/19-api-v3/02-organization.ts b/e2e/cypress/tests/19-api-v3/02-organization.ts new file mode 100644 index 000000000..4a233992f --- /dev/null +++ b/e2e/cypress/tests/19-api-v3/02-organization.ts @@ -0,0 +1,190 @@ +describe('Organization', () => { + before(() => { + cy.loginByAuthAPI('', '').then((token_res: any) => { + cy.setHeaders({ 'Content-Type': 'application/json' }) + cy.setAuthorizationToken(token_res.token) + }) + }) + + it('GET /organizations', () => { + cy.callAPI('ds/api/v3/organizations', 'GET').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + expect( + body.filter((o: any) => o.name == 'ministry-of-health').length + ).to.be.equal(1) + } + ) + }) + it('GET /organizations/{org}', () => { + cy.callAPI('ds/api/v3/organizations/ministry-of-health', 'GET').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + expect( + body.orgUnits.filter((o: any) => o.name == 'public-health').length + ).to.be.equal(1) + } + ) + }) + + it('GET /organizations/{org}/roles', () => { + const match = { + name: 'ministry-of-health', + parent: '/ca.bc.gov', + roles: [ + { + name: 'organization-admin', + permissions: [ + { + resource: 'org/ministry-of-health', + scopes: ['Dataset.Manage', 'GroupAccess.Manage', 'Namespace.Assign'], + }, + ], + }, + ], + } + + cy.callAPI('ds/api/v3/organizations/ministry-of-health/roles', 'GET').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + } + ) + }) + + it('GET /organizations/{org}/access', () => { + // ignore specific member contents since previous tests will have created members + const match = { + name: 'ministry-of-health', + parent: '/ca.bc.gov', + }; + + cy.callAPI('ds/api/v3/organizations/ministry-of-health/access', 'GET').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200); + expect(body).to.include(match); + expect(body.members).to.be.an('array'); + } + ) + }) + + it('PUT /organizations/{org}/access', () => { + const payload = { + name: 'planning-and-innovation-division', + parent: '/ca.bc.gov/ministry-of-health', + members: [ + { + member: { + email: 'mark@gmail.com', + }, + roles: ['organization-admin'], + }, + ], + } + cy.setRequestBody(payload) + + cy.callAPI( + 'ds/api/v3/organizations/planning-and-innovation-division/access', + 'PUT' + ).then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(204) + + cy.callAPI( + 'ds/api/v3/organizations/planning-and-innovation-division/access', + 'GET' + ).then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + + const match = { + name: 'planning-and-innovation-division', + parent: '/ca.bc.gov/ministry-of-health', + members: [ + { + member: { + username: 'mark@idir', + email: 'mark@gmail.com', + }, + roles: ['organization-admin'], + }, + ], + } + // ignore the ID as it will always be different + body.members.forEach((m: any) => { + delete m.member.id + }) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + }) + }) + }) + + it('GET /organizations/{org}/gateways', () => { + const match = { + name: 'platform', + orgUnit: 'planning-and-innovation-division', + enabled: false, + updatedAt: 0, + } + + cy.callAPI('ds/api/v3/organizations/ministry-of-health/gateways', 'GET').then( + ({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + expect( + JSON.stringify(body.filter((a: any) => a.name == 'platform').pop()) + ).to.be.equal(JSON.stringify(match)) + } + ) + }) + + it('GET /organizations/{org}/activity', () => { + // Retry logic if the 422 error occurs + cy.callAPI('ds/api/v3/organizations/ministry-of-health/activity', 'GET') + .then(({ apiRes: { status, body } }: any) => { + if (status === 422) { + cy.wait(2000); + cy.callAPI('ds/api/v3/organizations/ministry-of-health/activity', 'GET') + .then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200); + }); + } else { + expect(status).to.be.equal(200); + } + }); + }); + + it('PUT /organizations/{org}/{orgUnit}/gateways/{gatewayId}', () => { + cy.setRequestBody({}) + cy.callAPI('ds/api/v3/gateways', 'POST').then(({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + const myGateway = body + + cy.setRequestBody({}) + cy.callAPI( + `ds/api/v3/organizations/ministry-of-health/planning-and-innovation-division/gateways/${myGateway.gatewayId}?enable=true`, + 'PUT' + ).then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + expect(body.result).to.be.equal('namespace-assigned') + }) + }) + }) + + it('GET /roles', () => { + const match: any = { + 'organization-admin': { + label: 'Organization Administrator', + permissions: [ + { + resourceType: 'organization', + scopes: ['GroupAccess.Manage', 'Namespace.Assign', 'Dataset.Manage'], + }, + { resourceType: 'namespace', scopes: ['Namespace.View'] }, + ], + }, + } + + cy.callAPI('ds/api/v3/roles', 'GET').then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + }) + }) +}) diff --git a/e2e/cypress/tests/19-api-v3/03-gateways.ts b/e2e/cypress/tests/19-api-v3/03-gateways.ts new file mode 100644 index 000000000..09db841b3 --- /dev/null +++ b/e2e/cypress/tests/19-api-v3/03-gateways.ts @@ -0,0 +1,245 @@ +describe('Gateways', () => { + let workingData: any + + before(() => { + cy.buildOrgGatewayDatasetAndProduct().then((data) => { + workingData = data + }) + }) + + describe('Happy Paths', () => { + it('POST /gateways', () => { + const payload = { + displayName: 'My ABC Gateway', + } + cy.setRequestBody(payload) + cy.callAPI('ds/api/v3/gateways', 'POST').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body, null, 2)) + expect(body.displayName).to.be.equal(payload.displayName) + + const gateway = body + + cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}`, 'GET').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body, null, 2)) + expect(body.displayName).to.be.equal(gateway.displayName) + } + ) + + cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}/activity`, 'GET').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body, null, 2)) + expect(body.length).to.be.equal(1) + expect(body[0].message).to.be.equal('{actor} created {ns} namespace') + expect(body[0].params.ns).to.be.equal(gateway.gatewayId) + } + ) + } + ) + }) + + it('POST /gateways (with no payload)', () => { + const { v4: uuidv4 } = require('uuid') + const payload = {} + cy.log(JSON.stringify(payload)) + cy.setRequestBody(payload) + cy.callAPI('ds/api/v3/gateways', 'POST').then( + ({ apiRes: { body, status } }: any) => { + const match = { + gatewayId: body.gatewayId, + displayName: "janis's Gateway", + } + expect(status).to.be.equal(200) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + } + ) + }) + + it('POST /gateways (with gatewayId)', () => { + const { v4: uuidv4 } = require('uuid') + const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) + + const payload = { + gatewayId: `custom-${customId}-gw`, + displayName: 'ABC GW', + } + cy.log(JSON.stringify(payload)) + cy.setRequestBody(payload) + cy.callAPI('ds/api/v3/gateways', 'POST').then( + ({ apiRes: { body, status } }: any) => { + cy.log(body) + expect(status).to.be.equal(200) + expect(body.gatewayId).to.be.equal(payload.gatewayId) + expect(body.displayName).to.be.equal(payload.displayName) + } + ) + }) + + it('POST /gateways (with all valid chars)', () => { + const { v4: uuidv4 } = require('uuid') + const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) + + const payload = { + gatewayId: `custom-${customId}-gw`, + displayName: 'ABC GW with ( ) - _ / . chars', + } + cy.log(JSON.stringify(payload)) + cy.setRequestBody(payload) + cy.callAPI('ds/api/v3/gateways', 'POST').then( + ({ apiRes: { body, status } }: any) => { + cy.log(body) + expect(status).to.be.equal(200) + expect(body.gatewayId).to.be.equal(payload.gatewayId) + expect(body.displayName).to.be.equal(payload.displayName) + } + ) + }) + + it('POST /gateways (no displayname)', () => { + const { v4: uuidv4 } = require('uuid') + const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 10) + const payload = { + gatewayId: `a${customId}a`, + } + cy.log(JSON.stringify(payload)) + cy.setRequestBody(payload) + cy.callAPI('ds/api/v3/gateways', 'POST').then( + ({ apiRes: { body, status } }: any) => { + const match = { + gatewayId: payload.gatewayId, + displayName: "janis's Gateway", + } + expect(status).to.be.equal(200) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + } + ) + }) + + it('GET /gateways', () => { + const { gateway } = workingData + cy.callAPI(`ds/api/v3/gateways`, 'GET').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body, null, 2)) + + // Look for the specific gateway in the response body + const foundGateway = body.find( + (item: { gatewayId: string, displayName: string }) => + item.gatewayId === gateway.gatewayId && item.displayName === gateway.displayName + ); + + // Assert that the gateway was found + expect(foundGateway).to.not.be.undefined; + cy.log(`Found gateway: ${JSON.stringify(foundGateway, null, 2)}`); + } + ) + }) + + it('GET /gateways/{gatewayId}', () => { + const { gateway } = workingData + cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}`, 'GET').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body, null, 2)) + expect(body.displayName).to.be.equal(gateway.displayName) + } + ) + }) + + it('GET /gateways/{gatewayId}/activity', () => { + const { gateway } = workingData + cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}/activity`, 'GET').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body, null, 2)) + expect(body.length).to.be.equal(3) + expect(body[2].message).to.be.equal('{actor} created {ns} namespace') + expect(body[2].params.ns).to.be.equal(gateway.gatewayId) + } + ) + }) + + // it('DELETE /gateways/{gatewayId}', () => { + // const { gateway } = workingData + // cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}`, 'DELETE').then( + // ({ apiRes: { body, status } }: any) => { + // expect(status).to.be.equal(200) + // cy.log(JSON.stringify(body, null, 2)) + // } + // ) + // }) + }) + describe('Error Paths', () => { + it('POST /gateways (bad gatewayId)', () => { + const payload = { + gatewayId: `CAP-LETTERS`, + displayName: 'My ABC Gateway', + } + cy.log(JSON.stringify(payload)) + cy.setRequestBody(payload) + cy.callAPI('ds/api/v3/gateways', 'POST').then( + ({ apiRes: { body, status } }: any) => { + const match = { + message: 'Validation Failed', + details: { + d0: { + message: + 'Namespace name must be between 5 and 15 alpha-numeric lowercase characters and start and end with an alphabet.', + }, + }, + } + expect(status).to.be.equal(422) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + } + ) + }) + it('POST /gateways (display name too long)', () => { + const payload = { + displayName: 'this display name is more than 30 characters', + } + cy.log(JSON.stringify(payload)) + cy.setRequestBody(payload) + cy.callAPI('ds/api/v3/gateways', 'POST').then( + ({ apiRes: { body, status } }: any) => { + const match = { + message: 'Validation Failed', + details: { + d0: { + message: + 'Display name must be between 3 and 30 characters, starting with an alpha-numeric character, and can only use special characters "-()_ .\'/".', + }, + }, + } + expect(status).to.be.equal(422) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + } + ) + }) + it('POST /gateways (display name invalid characters)', () => { + const payload = { + displayName: 'this display name has invalid # char', + } + cy.log(JSON.stringify(payload)) + cy.setRequestBody(payload) + cy.callAPI('ds/api/v3/gateways', 'POST').then( + ({ apiRes: { body, status } }: any) => { + const match = { + message: 'Validation Failed', + details: { + d0: { + message: + 'Display name must be between 3 and 30 characters, starting with an alpha-numeric character, and can only use special characters "-()_ .\'/".', + }, + }, + } + expect(status).to.be.equal(422) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + } + ) + }) + }) +}) diff --git a/e2e/cypress/tests/19-api-v3/04-products.ts b/e2e/cypress/tests/19-api-v3/04-products.ts new file mode 100644 index 000000000..3815d68a2 --- /dev/null +++ b/e2e/cypress/tests/19-api-v3/04-products.ts @@ -0,0 +1,45 @@ +describe('Products', () => { + let workingData: any + + before(() => { + cy.buildOrgGatewayDatasetAndProduct().then((data) => { + workingData = data + }) + }) + + describe('Happy Paths', () => { + it('PUT /gateways/{gatewayId}/products', () => { + const { gateway } = workingData + cy.setRequestBody({ + name: `my-product-on-${gateway.gatewayId}`, + environments: [ + { + name: 'dev', + active: false, + approval: false, + flow: 'public', + }, + ], + }) + cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}/products`, 'PUT').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body)) + } + ) + }) + + it('GET /gateways/{gatewayId}/products', () => { + const { gateway } = workingData + cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}/products`, 'GET').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body, null, 2)) + expect(body.length).to.be.equal(1) + expect(body[0].name).to.be.equal(`my-product-on-${gateway.gatewayId}`) + expect(body[0].environments.length).to.be.equal(1) + } + ) + }) + }) +}) diff --git a/e2e/cypress/tests/19-api-v3/05-issuers.ts b/e2e/cypress/tests/19-api-v3/05-issuers.ts new file mode 100644 index 000000000..9e50c781e --- /dev/null +++ b/e2e/cypress/tests/19-api-v3/05-issuers.ts @@ -0,0 +1,51 @@ +describe('Authorization Profiles', () => { + let workingData: any + + before(() => { + cy.buildOrgGatewayDatasetAndProduct().then((data) => { + workingData = data + }) + }) + + describe('Happy Paths', () => { + it('PUT /gateways/{gatewayId}/issuers', () => { + const { gateway } = workingData + cy.setRequestBody({ + name: `my-auth-profile-for-${gateway.gatewayId}`, + description: 'Auth connection to my IdP', + flow: 'client-credentials', + clientAuthenticator: 'client-secret', + mode: 'auto', + inheritFrom: 'Sample Shared IdP', + }) + cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}/issuers`, 'PUT').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body)) + } + ) + }) + + it('GET /gateways/{gatewayId}/issuers', () => { + const { gateway } = workingData + cy.callAPI(`ds/api/v3/gateways/${gateway.gatewayId}/issuers`, 'GET').then( + ({ apiRes: { body, status } }: any) => { + expect(status).to.be.equal(200) + cy.log(JSON.stringify(body, null, 2)) + expect(body.length).to.be.equal(1) + + const issuer = body[0] + + expect(issuer.name).to.be.equal(`my-auth-profile-for-${gateway.gatewayId}`) + expect(issuer.environmentDetails[1].environment).to.be.equal('test') + expect(issuer.environmentDetails[1].issuerUrl).to.be.equal( + Cypress.env('OIDC_ISSUER') + ) + expect(issuer.environmentDetails[1].clientId).to.be.equal( + `ap-my-auth-profile-for-${gateway.gatewayId}-test` + ) + } + ) + }) + }) +}) diff --git a/e2e/cypress/tests/19-api-v3/06-identifiers.ts b/e2e/cypress/tests/19-api-v3/06-identifiers.ts new file mode 100644 index 000000000..421770b21 --- /dev/null +++ b/e2e/cypress/tests/19-api-v3/06-identifiers.ts @@ -0,0 +1,37 @@ +describe('Identifiers', () => { + it('GET /identifiers/application', () => { + cy.callAPI('ds/api/v3/identifiers/application', 'GET').then( + ({ apiRes: { status, body } }: any) => { + cy.log(`ID ${body}`) + expect(status).to.be.equal(200) + } + ) + }) + + it('GET /identifiers/product', () => { + cy.callAPI('ds/api/v3/identifiers/product', 'GET').then( + ({ apiRes: { status, body } }: any) => { + cy.log(`ID ${body}`) + expect(status).to.be.equal(200) + } + ) + }) + + it('GET /identifiers/environment', () => { + cy.callAPI('ds/api/v3/identifiers/environment', 'GET').then( + ({ apiRes: { status, body } }: any) => { + cy.log(`ID ${body}`) + expect(status).to.be.equal(200) + } + ) + }) + + it('GET /identifiers/gateway', () => { + cy.callAPI('ds/api/v3/identifiers/gateway', 'GET').then( + ({ apiRes: { status, body } }: any) => { + cy.log(`ID ${body}`) + expect(status).to.be.equal(200) + } + ) + }) +}) diff --git a/e2e/cypress/tests/19-api-v3/07-endpoints.ts b/e2e/cypress/tests/19-api-v3/07-endpoints.ts new file mode 100644 index 000000000..c2e8d47e8 --- /dev/null +++ b/e2e/cypress/tests/19-api-v3/07-endpoints.ts @@ -0,0 +1,101 @@ +describe('Endpoints - unused service', () => { + it('GET /routes/availability', () => { + cy.callAPI( + 'ds/api/v3/routes/availability?gatewayId=gw-1234&serviceName=testme', + 'GET' + ).then(({ apiRes: { status, body } }: any) => { + const match = { + available: true, + suggestion: { + serviceName: 'testme', + names: ['testme', 'testme-dev', 'testme-test'], + hosts: [ + 'testme.api.gov.bc.ca', + 'testme.dev.api.gov.bc.ca', + 'testme.test.api.gov.bc.ca', + 'testme-api-gov-bc-ca.dev.api.gov.bc.ca', + 'testme-api-gov-bc-ca.test.api.gov.bc.ca', + ], + }, + } + expect(status).to.be.equal(200) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + }) + }) +}) + +describe('Endpoints - used service', () => { + let userSession: any + + beforeEach(() => { + cy.fixture('common-testdata').as('common-testdata') + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.get('@common-testdata').then(({ serviceAvailability }: any) => { + cy.getUserSessionTokenValue(serviceAvailability.namespace, false).then((value) => { + console.log(value) + userSession = value + }) + }) + }) + + it('Check gwa config command to set environment', () => { + var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, '') + cy.executeCliCommand('gwa config set --host ' + cleanedUrl + ' --scheme http').then( + (response) => { + expect(response.stdout).to.contain('Config settings saved') + } + ) + }) + + it('Check gwa config command to set token', () => { + cy.executeCliCommand('gwa config set --token ' + userSession).then((response) => { + expect(response.stdout).to.contain('Config settings saved') + }) + }) + + it('create namespace with cli', () => { + cy.get('@common-testdata').then(({ serviceAvailability }: any) => { + cy.executeCliCommand( + 'gwa gateway create --gateway-id ' + serviceAvailability.namespace + ' --display-name="Service Availability"' + ).then((response) => { + expect(response.stdout).to.contain(serviceAvailability.namespace) + }) + }) + }) + + it('Upload config for key-auth', () => { + cy.executeCliCommand('gwa apply -i ./cypress/fixtures/service-availability.yml').then((response) => { + expect(response.stdout).to.contain('Gateway Services published'); + }) + }) + + it('GET /routes/availability', () => { + cy.callAPI( + 'ds/api/v3/routes/availability?gatewayId=gw-1234&serviceName=taken-service-name', + 'GET' + ).then(({ apiRes: { status, body } }: any) => { + const match = { + available: false, + suggestion: { + serviceName: "gw-1234-taken-service-name", + names: [ + "gw-1234-taken-service-name", + "gw-1234-taken-service-name-dev", + "gw-1234-taken-service-name-test" + ], + hosts: [ + "gw-1234-taken-service-name.api.gov.bc.ca", + "gw-1234-taken-service-name.dev.api.gov.bc.ca", + "gw-1234-taken-service-name.test.api.gov.bc.ca", + "gw-1234-taken-service-name-api-gov-bc-ca.dev.api.gov.bc.ca", + "gw-1234-taken-service-name-api-gov-bc-ca.test.api.gov.bc.ca" + ] + }, + } + expect(status).to.be.equal(200) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + }) + }) +}) \ No newline at end of file diff --git a/e2e/cypress/tests/20-gateways/01-list.ts b/e2e/cypress/tests/20-gateways/01-list.ts new file mode 100644 index 000000000..6e7f5eb3b --- /dev/null +++ b/e2e/cypress/tests/20-gateways/01-list.ts @@ -0,0 +1,144 @@ +import NameSpacePage from '../../pageObjects/namespace' +let gateways: any + +const { v4: uuidv4 } = require('uuid') +const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) + +describe('My Gateways list page', () => { + const ns = new NameSpacePage() + let userSession: any + + before(() => { + cy.deleteAllCookies() + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + cy.fixture('common-testdata').as('common-testdata') + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.getUserSessionTokenValue('', false).then((value) => { + userSession = value + }) + }) + + it('Set token with gwa config command', () => { + cy.exec('gwa config set --token ' + userSession, { timeout: 3000, failOnNonZeroExit: false }).then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('create a set of namespaces', () => { + cy.get('@common-testdata').then(({ myGateways }: any) => { + gateways = myGateways + Cypress._.forEach(gateways, (gateway) => { + cy.createGateway(gateway.gatewayId + '-' + customId, gateway.displayName); + }); + }); + }); + + it('Verify My Gateways shows the created gateways', () => { + cy.visit(ns.listPath) + Cypress._.forEach(gateways, (gateway) => { + cy.get(`[data-testid="ns-list-item-${gateway.gatewayId + '-' + customId}"]`) + .should('contain.text', gateway.displayName) + }); + }) + + it('Verify redirect to My Gateways page if no gateway selected', () => { + cy.visit(ns.detailPath) + cy.wait(2000) + cy.verifyToastMessage('First select a Gateway to view that page') + }) + + it('Check Gateway link goes to details page', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.url().should('include', '/manager/gateways/detail') + cy.get('h1').should('contain.text', gateways["namespace1"].displayName) + }) + + it('Test search - find results', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-search-input"]').type(gateways["namespace1"].displayName) + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + cy.get(`[data-testid="ns-list-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get('[data-testid="ns-search-input"]').clear() + cy.get(`[data-testid="ns-list-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('exist') + }) + + it('Test search - do not find results', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-search-input"]').type('gibberish') + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get('[data-testid="ns-no-results-text"]').should('exist') + }) + + it('Test filter - find results', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-filter-select"]').select('disabled') + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + }) + + it('Test filter - do not find results', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-filter-select"]').select('pending') + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('not.exist') + }) + + it('Test filter and search together', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-filter-select"]').select('disabled') + cy.get('[data-testid="ns-search-input"]').type(gateways["namespace1"].displayName) + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + }) + + it('Edit Gateway display name - valid', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.get('[data-testid="display-name-edit-btn"]').click() + cy.get('[data-testid="edit-display-name-input"]').type(' Pie') + cy.get('[data-testid="edit-display-name-submit-btn"]').click() + cy.get('h1').should('contain.text', 'Apple Pie') + }) + + it('Edit Gateway display name - too short or too long', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.get('[data-testid="display-name-edit-btn"]').click() + cy.get('[data-testid="edit-display-name-input"]').clear().type('12') + cy.get('[data-testid="edit-display-name-submit-btn"]').should('be.disabled') + cy.get('[data-testid="edit-display-name-input"]').clear().type('Supercalifragilisticexpialidocious') + cy.get('[data-testid="edit-display-name-submit-btn"]').should('be.disabled') + cy.get('[data-testid="edit-display-name-input"]').clear().type('A reasonable name') + cy.get('[data-testid="edit-display-name-submit-btn"]').should('be.enabled') + cy.get('[data-testid="edit-display-name-cancel-btn"]').click() + }) + + it('Export Gateway Report', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-report-btn"]').click() + cy.get('[data-testid="export-report-select-all-check"]').click() + cy.get('[data-testid="export-report-export-btn"]').click() + // check that the report is downloaded + const filePath = 'cypress/downloads/gateway-report.xlsx' + cy.readFile(filePath, 'binary', { timeout: 10000 }).then((fileContent) => { + expect(fileContent.length).to.be.greaterThan(0); + }); + }); + + it('Cleanup: delete namespaces', () => { + Cypress._.forEach(gateways, (gateway) => { + cy.deleteGatewayCli(gateway.gatewayId + '-' + customId, false) + }); + }) + + after(() => { + cy.logout() + cy.clearLocalStorage({ log: true }) + cy.deleteAllCookies() + }) + +}) \ No newline at end of file diff --git a/e2e/cypress/tests/20-gateways/02-dropdown.ts b/e2e/cypress/tests/20-gateways/02-dropdown.ts new file mode 100644 index 000000000..8841c581c --- /dev/null +++ b/e2e/cypress/tests/20-gateways/02-dropdown.ts @@ -0,0 +1,130 @@ +import NameSpacePage from '../../pageObjects/namespace' +import ApiDirectoryPage from '../../pageObjects/apiDirectory' +let gateways: any +let gateway1: any +let totalGateways: number + +const { v4: uuidv4 } = require('uuid') +const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) + +describe('Gateway selector dropdown', () => { + const ns = new NameSpacePage() + const ad = new ApiDirectoryPage() + let userSession: any + + before(() => { + cy.deleteAllCookies() + cy.clearLocalStorage({ log: true }) + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + cy.fixture('common-testdata').as('common-testdata') + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.getUserSessionTokenValue('', false).then((value) => { + userSession = value + }) + }) + + it('Set token with gwa config command', () => { + cy.exec('gwa config set --token ' + userSession, { timeout: 3000, failOnNonZeroExit: false }).then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('Get current total number of gateways', () => { + // Create a new gateway to ensure there is at least one gateway + cy.createGateway(); + + cy.visit(ad.yourProductsPath); + cy.get('[data-testid="ns-dropdown-btn"]').click(); + cy.get('[data-testid="ns-dropdown-total-gateways"]').should('be.visible').then(($el) => { + const totalText = $el.text(); + totalGateways = parseInt( + totalText.replace(/You have /, '').replace(/ Gateways in total/, '') + ); + cy.log('Existing gateways: ' + totalGateways); + }); + }) + + it('create a set of namespaces', () => { + cy.get('@common-testdata').then(({ myGateways }: any) => { + gateways = myGateways + gateway1 = gateways["namespace1"] + Cypress._.forEach(gateways, (gateway) => { + cy.createGateway(gateway.gatewayId + '-' + customId, gateway.displayName); + }); + }); + }); + + it('Verify dropdown shows the new total number of gateways', () => { + cy.visit(ad.yourProductsPath) + cy.get('[data-testid="ns-dropdown-btn"]').should('contain.text', "No Active Gateway") + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get('[data-testid="ns-dropdown-total-gateways"]').should('contain.text', `You have ${totalGateways + 4} Gateways in total`) + }) + + it('Check Gateway button activates the Gateway', () => { + cy.visit(ad.yourProductsPath) + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.get('h1').should('contain.text', gateways["namespace1"].displayName) + + cy.visit(ns.detailPath) + cy.url().should('include', '/manager/gateways/detail') + cy.get('[data-testid="ns-detail-gateway-display-name"]').should('contain.text', gateways["namespace1"].displayName) + }) + + it('Recently used gateways are shown in the dropdown', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).click() + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get('[data-testid="ns-dropdown-heading"]').should('contain.text', "Recently viewed") + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace3"].gatewayId + '-' + customId}"]`).should('not.exist') + }) + + it('Test search - find results', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace2"].gatewayId + '-' + customId}"]`).click() + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace3"].gatewayId + '-' + customId}"]`).click() + + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get('[data-testid="ns-dropdown-search-input"]').type(gateway1.displayName) + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace3"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get('[data-testid="ns-dropdown-search-input"]').clear() + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('exist') + }) + + it('Test filter - do not find results', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get('[data-testid="ns-dropdown-search-input"]').type('gibberish') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get('[data-testid="ns-dropdown-no-results-box"]').should('exist') + }) + + it('Cleanup: delete namespaces', () => { + Cypress._.forEach(gateways, (gateway) => { + cy.deleteGatewayCli(gateway.gatewayId + '-' + customId, false) + }); + }) + + after(() => { + // cy.logout() + cy.clearLocalStorage({ log: true }) + cy.deleteAllCookies() + }) + +}) \ No newline at end of file diff --git a/e2e/package-lock.json b/e2e/package-lock.json index da494f9c1..5c265fe6b 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -26,6 +26,7 @@ "npm-run-all": "^4.1.5", "request": "^2.88.2", "typescript": "^4.3.5", + "uuid": "^8.3.2", "yaml": "^2.1.3", "yamljs": "^0.3.0" }, @@ -119,11 +120,11 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -131,28 +132,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -176,11 +177,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.24.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -190,12 +191,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -213,57 +214,61 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -273,78 +278,78 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -410,9 +415,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -421,31 +426,31 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -462,12 +467,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -875,9 +880,9 @@ } }, "node_modules/@types/node": { - "version": "16.18.97", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.97.tgz", - "integrity": "sha512-4muilE1Lbfn57unR+/nT9AFjWk0MtWi5muwCEJqnOvfRQDbSfLCUdN7vCIg8TYuaANfhLOV85ve+FNpiUsbSRg==", + "version": "16.18.98", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.98.tgz", + "integrity": "sha512-fpiC20NvLpTLAzo3oVBKIqBGR6Fx/8oAK/SSf7G+fydnXMY1x4x9RZ6sBXhqKlCU21g2QapUsbLlhv3+a7wS+Q==", "dev": true }, "node_modules/@types/request": { @@ -1884,6 +1889,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1968,9 +1974,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001621", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz", - "integrity": "sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA==", + "version": "1.0.30001628", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001628.tgz", + "integrity": "sha512-S3BnR4Kh26TBxbi5t5kpbcUlLJb9lhtDXISDPwOfI+JoC+ik0QksvkZtUVyikw3hjnkgkMPSJ8oIM9yMm9vflA==", "funding": [ { "type": "opencollective", @@ -2386,9 +2392,9 @@ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/cypress": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.10.0.tgz", - "integrity": "sha512-tOhwRlurVOQbMduX+KonoMeQILs2cwR3yHGGENoFvvSoLUBHmJ8b9/n21gFSDqjlOJ+SRVcwuh+fG/JDsHsT6Q==", + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.11.0.tgz", + "integrity": "sha512-NXXogbAxVlVje4XHX+Cx5eMFZv4Dho/2rIcdBHg9CNPFUGZdM4cRdgIgM7USmNYsC12XY0bZENEQ+KBk72fl+A==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2844,9 +2850,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.779", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.779.tgz", - "integrity": "sha512-oaTiIcszNfySXVJzKcjxd2YjPxziAd+GmXyb2HbidCeFo6Z88ygOT7EimlrEQhM2U08VhSrbKhLOXP0kKUCZ6g==" + "version": "1.4.790", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.790.tgz", + "integrity": "sha512-eVGeQxpaBYbomDBa/Mehrs28MdvCXfJmEFzaMFsv8jH/MJDLIylJN81eTJ5kvx7B7p18OiPK0BkC06lydEy63A==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -3981,6 +3987,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7184,6 +7191,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dependencies": { "glob": "^7.1.3" }, @@ -7684,9 +7692,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==" + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==" }, "node_modules/sprintf-js": { "version": "1.1.3", @@ -8062,9 +8070,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true }, "node_modules/tsutils": { @@ -8646,9 +8654,9 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yaml": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", - "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.3.tgz", + "integrity": "sha512-sntgmxj8o7DE7g/Qi60cqpLBA3HG3STcDA0kO+WfB05jEKhZMbY7umNm2rBpQvsmZ16/lPXCJGW2672dgOUkrg==", "bin": { "yaml": "bin.mjs" }, diff --git a/e2e/package.json b/e2e/package.json index a88a6812c..c8c5664b1 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -64,6 +64,7 @@ "npm-run-all": "^4.1.5", "request": "^2.88.2", "typescript": "^4.3.5", + "uuid": "^8.3.2", "yaml": "^2.1.3", "yamljs": "^0.3.0" } diff --git a/e2e/test-data-dependencies.md b/e2e/test-data-dependencies.md index 4cff415ea..519cb98e1 100644 --- a/e2e/test-data-dependencies.md +++ b/e2e/test-data-dependencies.md @@ -1,6 +1,7 @@ | Test | Dependencies | | ----------------------------------------------------------- | -------------------------------------------------------------- | | 01-api-key | | +| │   00-get-started.cy.ts | Must run without existing namespaces| | │   01-create-api.cy.ts | NA | | │   02-team-access.cy.ts | 1.1 | | │   03-request-access-inactive-env.cy.ts | 1.1 to 1.2 | @@ -59,8 +60,9 @@ | │   06-shared-idp.cy.ts | Folder 01-api-key and 02-client-credential-flow and 9.1 to 9.5 | | │   07-kong-public-auth.ts | Folder 01-api-key and 02-client-credential-flow and 9.1 to 9.6 | | │   08-protected-externally.ts | Folder 01-api-key and 02-client-credential-flow and 9.1 to 9.7 | +| │   09-two-tiered-hidden.cy.ts | NA | | 10-clear-resources | | -| │   01-create-api.cy.ts | | +| │   01-create-api.cy.ts | NA (CONFIRM: Is this accurate?) | | │   02-team-access.cy.ts | 10.1 | | │   03-rqst-access.cy.ts | 10.1 to 10.2 | | │   04-delete-consumer.ts | 10.1 to 10.3 | @@ -79,6 +81,7 @@ | │   07-namespace-view.cy.ts | 12.1 to 12.2 | | │   08-gateway-config.cy.ts | 12.1 to 12.2 | | │   09-content-publish.cy.ts | 12.1 to 12.2 | +| │   10-identity-provider.cy.ts | NA | | 13-namespace-preview-mode | | | │   01-create-api.cy.ts | NA | | │   02-namespace-preview-mode.cy.ts | 13.1 | @@ -99,11 +102,24 @@ | │   08-namespaces.cy.ts | 1.1 and 15.1 | | 16-gwa-cli | | | │   01-cli-commands.ts | NA | -| │   02-cli-generate-config.ts | 16.1 | +| │   02-cli-generate-config-quick-start.ts | 16.1 | +| │   03-cli-generate-config-client-cred.ts | 1.1, 2.1 to 2.3, 15.1, 15.5. 16.1 | +| │   04-cli-gateway-create.ts | NA | | 17-delete-application | | | │   01-delete-application-without-access.cy.ts | NA | -| │   02-delete-application-with-pending-request.cy.ts | 17.1 | +| │   02-delete-application-with-pending-request.cy.ts | 17.1 (CONFIRM: requires product, created in 01-api-key?) | | │   03-delete-application-with-approved-request.cy.ts | 17.1 to 17.2 | | │   04-delete-namespace-gwa.ts | 17.1 to 17.3 | | 18-scan-astra-result | | -|     01-store-and-scan-astra-result.ts | NA | \ No newline at end of file +| |   01-store-and-scan-astra-result.ts | NA | +| 19-api-v3 | | +| |   01-api-directory.cy.ts | ? | +| |   02-organization.cy.ts | ? | +| |   03-gateways.cy.ts | ? | +| |   04-products.cy.ts | ? | +| |   05-issuers.cy.ts | ? | +| |   06-identifiers.cy.ts | ? | +| |   07-endpoints.cy.ts | ? | +| 20-gateways | | +| |   01-list.cy.ts | NA | +| |   02-create.cy.ts | NA | \ No newline at end of file diff --git a/local/feeder-init/shared-idp.yaml b/local/feeder-init/shared-idp.yaml index 10e77d92d..1866a0fee 100644 --- a/local/feeder-init/shared-idp.yaml +++ b/local/feeder-init/shared-idp.yaml @@ -12,6 +12,11 @@ record: owner: janis@testmail.com isShared: true environmentDetails: + - environment: dev + issuerUrl: http://keycloak.localtest.me:9081/auth/realms/master + clientId: gwa-api + clientRegistration: managed + clientSecret: '18900468-3db1-43f7-a8af-e75f079eb742' - environment: test issuerUrl: http://keycloak.localtest.me:9081/auth/realms/master clientId: gwa-api diff --git a/local/keycloak/master-realm.json b/local/keycloak/master-realm.json index df8ae3277..338da6ef2 100644 --- a/local/keycloak/master-realm.json +++ b/local/keycloak/master-realm.json @@ -966,6 +966,45 @@ "notBefore": 0, "groups": [] }, + { + "id": "bf498a7b-b6e0-49bb-9ea8-0241d7792fe3", + "createdTimestamp": 1642463435902, + "username": "janis@github", + "enabled": true, + "totp": false, + "emailVerified": false, + "firstName": "Janis", + "lastName": "Smith", + "email": "janis@testmail.com", + "attributes": { + "provider_user_guid": ["220469E037C84A7ABDFAB15204A607C6"], + "identity_provider": ["github"], + "display_name": ["Janis@github"], + "provider_username": ["janis"] + }, + "credentials": [ + { + "id": "6aa0a7ca-c2ad-43f5-9bdb-3f58693b28bd", + "type": "password", + "createdDate": 1642466403247, + "secretData": "{\"value\":\"ltS/DMUYOCSmZZRbf7rYFTpLa9cqQDewFO1jwRPczU99leiyGtOCsbdAPRmSvLVhOLnzqvcaua3B17ej2d6/7w==\",\"salt\":\"ZrR/QMSRwruFRU5FsPUgHQ==\"}", + "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "uma_authorization", + "aps-admin", + "offline_access", + "api-owner" + ], + "clientRoles": { + "account": ["manage-account", "view-profile"] + }, + "notBefore": 0, + "groups": [] + }, { "id": "7f9dcdfc-6100-4ec8-ba3f-8bd26d184585", "createdTimestamp": 1623537499547, @@ -2071,7 +2110,7 @@ "logic": "POSITIVE", "decisionStrategy": "UNANIMOUS", "config": { - "scopes": "[\"GroupAccess.Manage\",\"Namespace.Assign\"]", + "scopes": "[\"GroupAccess.Manage\",\"Namespace.Assign\",\"Dataset.Manage\"]", "applyPolicies": "[\"janis\"]" } }, @@ -2317,9 +2356,7 @@ } } ], - "defaultClientScopes": [ - "profile" - ], + "defaultClientScopes": ["profile"], "optionalClientScopes": [] }, { diff --git a/local/oauth2-proxy/oauth2-proxy-dev.cfg b/local/oauth2-proxy/oauth2-proxy-dev.cfg new file mode 100644 index 000000000..80b0fa158 --- /dev/null +++ b/local/oauth2-proxy/oauth2-proxy-dev.cfg @@ -0,0 +1,27 @@ +http_address="0.0.0.0:4180" +cookie_secret="abcd1234!@#$$++=" +email_domains="*" +provider="oidc" +insecure_oidc_allow_unverified_email="true" +client_id="aps-portal" +client_secret="8e1a17ed-cb93-4806-ac32-e303d1c86018" +scope="openid" +oidc_issuer_url="http://keycloak.localtest.me:9081/auth/realms/master" +login_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/auth" +redeem_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/token" +validate_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo" +redirect_url="http://oauth2proxy.localtest.me:4180/oauth2/callback" +profile_url="http://keycloak.localtest.me:9081/auth/realms/master/protocol/openid-connect/userinfo" +cookie_secure="false" +cookie_refresh="3m" +cookie_expire="24h" +pass_basic_auth="false" +pass_access_token="true" +set_xauthrequest="true" +skip_jwt_bearer_tokens="false" +set_authorization_header="false" +pass_authorization_header="false" +skip_auth_regex="/__coverage__|/login|/health|/public|/docs|/redirect|/_next|/images|/devportal|/manager|/about|/maintenance|/admin/session|/ds/api|/gw/api|/feed/|/signout|^[/]$" +whitelist_domains="keycloak.localtest.me:9081" +upstreams=["http://portal.localtest.me:3000"] +skip_provider_button='true' diff --git a/src/api-openapi.js b/src/api-openapi.js index c84c07e31..115ba0274 100644 --- a/src/api-openapi.js +++ b/src/api-openapi.js @@ -31,7 +31,7 @@ class ApiOpenapiApp { } prepareV2(app) { - const { RegisterRoutes } = require('./controllers/v2/routes'); + const { RegisterRoutes: V2Register } = require('./controllers/v2/routes'); const specFile = fs.realpathSync('controllers/v2/openapi.yaml'); const specObject = YAML.load(fs.readFileSync(specFile)); @@ -50,7 +50,7 @@ class ApiOpenapiApp { specObject.components.securitySchemes.openid.openIdConnectUrl = `${process.env.OIDC_ISSUER}/.well-known/openid-configuration`; - RegisterRoutes(app); + V2Register(app); app.get('/ds/api/v2/openapi.yaml', (req, res) => { res.setHeader('Content-Type', 'application/yaml'); @@ -59,8 +59,31 @@ class ApiOpenapiApp { app.use( '/ds/api/v2/console', - swaggerUi.serve, - swaggerUi.setup(specObject, options) + swaggerUi.serveFiles(specObject, options), + swaggerUi.setup(specObject) + ); + } + + prepareV3(app) { + const { RegisterRoutes: V3Register } = require('./controllers/v3/routes'); + const specFile = fs.realpathSync('controllers/v3/openapi.yaml'); + const specObject = YAML.load(fs.readFileSync(specFile)); + + specObject.components.securitySchemes.jwt.flows.clientCredentials.tokenUrl = `${process.env.OIDC_ISSUER}/protocol/openid-connect/token`; + + specObject.components.securitySchemes.openid.openIdConnectUrl = `${process.env.OIDC_ISSUER}/.well-known/openid-configuration`; + + V3Register(app); + + app.get('/ds/api/v3/openapi.yaml', (req, res) => { + res.setHeader('Content-Type', 'application/yaml'); + res.send(YAML.dump(specObject)); + }); + + app.use( + '/ds/api/v3/console', + swaggerUi.serveFiles(specObject, options), + swaggerUi.setup(specObject) ); } @@ -72,13 +95,14 @@ class ApiOpenapiApp { Register(keystone); + this.prepareV3(app); this.prepareV2(app); this.prepareV1(app); // RFC 8631 service-desc link relation // https://datatracker.ietf.org/doc/html/rfc8631 app.get('/ds/api', (req, res) => { - res.setHeader('Link', '; rel="service-desc"'); + res.setHeader('Link', '; rel="service-desc"'); res.status(204).end(); }); diff --git a/src/api-proxy.js b/src/api-proxy.js index a12b26210..8e3c57376 100644 --- a/src/api-proxy.js +++ b/src/api-proxy.js @@ -17,7 +17,7 @@ class ApiProxyApp { logLevel: 'debug', pathRewrite: { '^/gw/api/v2/': '/v2/', - '^/gw/api/v3/': '/v3/', + '^/gw/api/v3/gateways/': '/v2/namespaces/' }, onProxyReq: (proxyReq, req) => { //console.log(req.headers) diff --git a/src/auth/auth-tsoa.ts b/src/auth/auth-tsoa.ts index 97ef15c86..69fc5bb92 100644 --- a/src/auth/auth-tsoa.ts +++ b/src/auth/auth-tsoa.ts @@ -55,6 +55,10 @@ export function expressAuthentication( resource = `org/${request.params.orgUnit}`; } else if ('org' in request.params) { resource = `org/${request.params.org}`; + } else if ('gatewayId' in request.params) { + resource = request.params.gatewayId; + } else if ('gatewayId' in request.query) { + resource = request.query.gatewayId; } else { // assume it is namespace-based protection resource = request.params.ns; @@ -78,7 +82,12 @@ export function expressAuthentication( request, { status: (s: number) => { - logger.error('invalid_token (%d) for %j', s, request.oauth_user); + logger.error( + 'invalid_token (%d) [%j] for %j', + s, + permissions, + request.oauth_user + ); reject( new UnauthorizedError('invalid_token', { message: `Missing authorization scope. (${s})`, diff --git a/src/authz/graphql-whitelist/httplocalhost4180-afc05a.gql b/src/authz/graphql-whitelist/httplocalhost4180-afc05a.gql index ffb1ee232..36abce8e3 100644 --- a/src/authz/graphql-whitelist/httplocalhost4180-afc05a.gql +++ b/src/authz/graphql-whitelist/httplocalhost4180-afc05a.gql @@ -1,7 +1,6 @@ query GetNamespaces { allNamespaces { - id name } } diff --git a/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-339568.gql b/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-339568.gql index 02c8bfd69..3b70f716e 100644 --- a/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-339568.gql +++ b/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-339568.gql @@ -1,7 +1,9 @@ query GetCurrentNamespace { currentNamespace { + id name + displayName org orgUnit orgUpdatedAt diff --git a/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-b2df18.gql b/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-b2df18.gql index ae6c0abe6..fa72e9122 100644 --- a/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-b2df18.gql +++ b/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-b2df18.gql @@ -3,6 +3,7 @@ allNamespaces { id name + displayName orgEnabled orgUpdatedAt } diff --git a/src/authz/graphql-whitelist/httpoauth2proxylocaltestme4180managernamespaces-a5ae8c.gql b/src/authz/graphql-whitelist/httpoauth2proxylocaltestme4180managernamespaces-a5ae8c.gql new file mode 100644 index 000000000..65ad9f325 --- /dev/null +++ b/src/authz/graphql-whitelist/httpoauth2proxylocaltestme4180managernamespaces-a5ae8c.gql @@ -0,0 +1,4 @@ + + mutation UpdateNamespaceDisplayName($displayName: String!) { + updateCurrentNamespaceDisplayName(displayName: $displayName) + } diff --git a/src/authz/graphql-whitelist/unknown-51c470.gql b/src/authz/graphql-whitelist/unknown-51c470.gql new file mode 100644 index 000000000..ea98aa726 --- /dev/null +++ b/src/authz/graphql-whitelist/unknown-51c470.gql @@ -0,0 +1,7 @@ + +query GetNamespaces { + allNamespaces { + id + name + } +} diff --git a/src/authz/matrix.csv b/src/authz/matrix.csv index 718639ecd..1582bd93c 100644 --- a/src/authz/matrix.csv +++ b/src/authz/matrix.csv @@ -73,6 +73,7 @@ NS MANAGER,,getConsumerProdEnvAccess,,,,,,,api-owner,,,allow, NS MANAGER,,allProductsByNamespace,,,,,,,api-owner,,,allow,filterByUserNS NS MANAGER,,allGatewayServicesByNamespace,,,,,,,api-owner,,,allow,filterByUserNS NS MANAGER,,getFilteredNamespaceConsumers,,,,,,,api-owner,,,allow,filterByUserNS +Any User,,,AccessRequest,update,,"isApproved,isIssued",,,,,,deny, API Owner Role Rules,,,AccessRequest,read,,,,,,,"api-owner,provider-user",allow, API Owner Role Rules,,,Activity,read,,,,,,,"api-owner,provider-user",allow,filterByUserNS API Owner Role Rules,,,Alert,,read,,,,,,"api-owner,provider-user",allow, @@ -128,6 +129,7 @@ API Owner Role Rules,,forceDeleteNamespace,,,,,,,api-owner,,,allow, API Owner Role Rules,,namespace,,,,,,,api-owner,,,allow, API Owner Role Rules,,currentNamespace,,,,,,,,,"portal-user,api-owner,provider-user,access-manager,credential-admin",allow, API Owner / Provider Role Rules,,updateCurrentNamespace,,,,,,,api-owner,,,allow, +API Owner / Provider Role Rules,,updateCurrentNamespaceDisplayName,,,,,,,api-owner,,,allow, API Owner Role Rules,,updatePermissions,,,,,,,api-owner,,,allow, API Owner Role Rules,,grantPermissions,,,,,,,api-owner,,,allow, API Owner Role Rules,,revokePermissions,,,,,,,api-owner,,,allow, diff --git a/src/babel.config.js b/src/babel.config.js index 4dc8ce035..be4a973b2 100644 --- a/src/babel.config.js +++ b/src/babel.config.js @@ -4,5 +4,5 @@ module.exports = { '@babel/preset-react', '@babel/preset-typescript', ], - plugins: ['transform-class-properties', 'istanbul'], + // plugins: ['transform-class-properties', 'istanbul'], }; diff --git a/src/batch/data-rules.js b/src/batch/data-rules.js index 1d243a22b..08fc326af 100644 --- a/src/batch/data-rules.js +++ b/src/batch/data-rules.js @@ -15,7 +15,7 @@ const metadata = { transformations: { tags: { name: 'toStringDefaultArray' }, orgUnits: { - name: 'connectExclusiveList', + name: 'connectExclusiveListCreate', list: 'OrganizationUnit', syncFirst: true, }, @@ -61,7 +61,7 @@ const metadata = { ], transformations: { tags: { name: 'toStringDefaultArray' }, - resources: { name: 'toString' }, + resources: { name: 'toStringDefaultArray' }, organization: { name: 'connectOne', key: 'organization.id', @@ -77,6 +77,16 @@ const metadata = { isInCatalog: { name: 'alwaysTrue' }, isDraft: { name: 'alwaysFalse' }, }, + validations: { + resources: { + type: 'entityArray', + entity: 'DatasetResource', + }, + contacts: { + type: 'entityArray', + entity: 'DatasetContact', + }, + }, }, DraftDataset: { entity: 'Dataset', @@ -101,6 +111,8 @@ const metadata = { ], transformations: { tags: { name: 'toStringDefaultArray' }, + resources: { name: 'toStringDefaultArray' }, + contacts: { name: 'toStringDefaultArray' }, organization: { name: 'connectOne', list: 'allOrganizations', @@ -152,8 +164,14 @@ const metadata = { }, isInCatalog: { type: 'boolean' }, isDraft: { type: 'boolean' }, - // contacts : TBD - // resources: TBD + resources: { + type: 'entityArray', + entity: 'DatasetResource', + }, + contacts: { + type: 'entityArray', + entity: 'DatasetContact', + }, }, example: { name: 'my_sample_dataset', @@ -207,6 +225,18 @@ const metadata = { // }, }, }, + Gateway: { + query: 'allNamespaces', + refKey: 'gatewayId', + sync: ['displayName'], + transformations: { + // members: { + // name: 'connectExclusiveList', + // list: 'MemberRole', + // syncFirst: true, + // }, + }, + }, MemberRole: { query: 'allMemberRoles', refKey: 'extRefId', @@ -696,6 +726,30 @@ const metadata = { sync: ['ref', 'type', 'blob'], transformations: {}, }, + DatasetContact: { + transient: true, + refKey: 'name', + sync: ['name', 'email', 'role'], + validations: { + role: { + type: 'enum', + values: ['pointOfContact'], + }, + }, + transformations: {}, + }, + DatasetResource: { + transient: true, + refKey: 'id', + sync: ['name', 'format', 'url'], + validations: { + format: { + type: 'enum', + values: ['openapi-json', 'json'], + }, + }, + transformations: {}, + }, }; module.exports.metadata = metadata; diff --git a/src/batch/feed-worker.ts b/src/batch/feed-worker.ts index 2d0ce490a..57086fcab 100644 --- a/src/batch/feed-worker.ts +++ b/src/batch/feed-worker.ts @@ -58,7 +58,6 @@ const transformations = { export const putFeedWorker = async (context: any, req: any, res: any) => { const entity = req.params['entity']; assert.strictEqual(entity in metadata, true); - logger.info('putFeedWorker %s', entity); const md = metadata[entity]; const refKey = md.refKey; @@ -267,7 +266,9 @@ export const getRecords = async function ( feedEntity: string, query: string = undefined, children: string[] = undefined, - where: BatchWhereClause = undefined + where: BatchWhereClause = undefined, + skip: number = 0, + first: number = 5000, ): Promise { const md = (metadata as any)[feedEntity]; @@ -276,7 +277,9 @@ export const getRecords = async function ( return await batchService.listAll( query ? query : md.query, buildQueryResponse(md, children), - where + where, + skip, + first ); }; @@ -472,6 +475,13 @@ export const syncRecords = async function ( json.hasOwnProperty(md['refKey']) && json[md['refKey']] != localRecord[md['refKey']] ) { + logger.error( + '[syncRecords] (%s) %s != %s', + md['refKey'], + json[md['refKey']], + localRecord[md['refKey']] + ); + throw new Error('Unexpected ' + md['refKey']); } const transformKeys = @@ -613,7 +623,7 @@ export const applyTransformationsToNewCreation = async ( transformInfo: any, inputData: any, parentRecord: any -) => { +): Promise => { if (!inputData) { return; } @@ -627,36 +637,52 @@ export const applyTransformationsToNewCreation = async ( const transformKeys = 'transformations' in md ? Object.keys(md.transformations) : []; - for (const inputDataRecord of inputData) { - for (const transformKey of transformKeys) { - logger.debug( - ' -- (applyTransformations) changed trans? (%s)', - transformKey - ); - const transformInfo = md.transformations[transformKey]; + return Promise.all( + inputData.map(async (inputDataRecord: any) => { + const data: any = {}; + for (const field of md.sync) { + if (field in inputDataRecord) { + data[field] = inputDataRecord[field]; + } + } - if (transformInfo.filterByNamespace && parentRecord) { - inputDataRecord['_namespace'] = parentRecord['namespace']; + if (inputDataRecord.hasOwnProperty(md.refKey)) { + data[md.refKey] = inputDataRecord[md.refKey]; + } else if (inputDataRecord.hasOwnProperty('id')) { + data[md.refKey] = inputDataRecord['id']; } - const transformMutation = await transformations[transformInfo.name]( - keystone, - transformInfo, - null, - inputDataRecord, - transformKey - ); - delete inputDataRecord['_namespace']; - if (transformMutation && transformMutation != null) { + for (const transformKey of transformKeys) { logger.debug( - ' -- (applyTransformations) trans (%s) %j', - transformKey, - transformMutation + ' -- (applyTransformations) changed trans? (%s)', + transformKey + ); + const transformInfo = md.transformations[transformKey]; + + if (transformInfo.filterByNamespace && parentRecord) { + inputDataRecord['_namespace'] = parentRecord['namespace']; + } + + const transformMutation = await transformations[transformInfo.name]( + keystone, + transformInfo, + null, + inputDataRecord, + transformKey ); - inputDataRecord[transformKey] = transformMutation; + delete inputDataRecord['_namespace']; + if (transformMutation && transformMutation != null) { + logger.debug( + ' -- (applyTransformations) trans (%s) %j', + transformKey, + transformMutation + ); + data[transformKey] = transformMutation; + } } - } - } + return data; + }) + ); }; export const removeEmpty = (obj: object) => { @@ -677,6 +703,17 @@ export const removeKeys = (obj: object, keys: string[]) => { return obj; }; +export const replaceKey = (obj: object, oldKey: string, newKey: string) => { + Object.entries(obj).forEach( + ([key, val]) => + (oldKey == key && + delete (obj as any)[key] && + ((obj as any)[newKey] = val)) || + (val && typeof val === 'object' && replaceKey(val, oldKey, newKey)) + ); + return obj; +}; + export const removeAllButKeys = (obj: object, keys: string[]) => { Object.entries(obj).forEach( ([key, val]) => diff --git a/src/batch/transformations/connectExclusiveListCreate.ts b/src/batch/transformations/connectExclusiveListCreate.ts index 88fa1949c..dec74db57 100644 --- a/src/batch/transformations/connectExclusiveListCreate.ts +++ b/src/batch/transformations/connectExclusiveListCreate.ts @@ -16,7 +16,7 @@ export async function connectExclusiveListCreate( ) { logger.debug('%s %j %j %j', fieldKey, currentData, inputData, parentRecord); - await applyTransformationsToNewCreation( + const createInputData = await applyTransformationsToNewCreation( keystone, transformInfo, inputData[fieldKey], @@ -35,7 +35,7 @@ export async function connectExclusiveListCreate( if (inputData[fieldKey]) { return { - create: inputData[fieldKey], + create: createInputData, }; } else { return null; diff --git a/src/controllers/ioc/keystoneInjector.ts b/src/controllers/ioc/keystoneInjector.ts index a29f9dd4c..1da31cf8d 100644 --- a/src/controllers/ioc/keystoneInjector.ts +++ b/src/controllers/ioc/keystoneInjector.ts @@ -47,7 +47,7 @@ export class KeystoneService { id: null, name: resolveName(request.user), username: resolveUsername(request.user), - namespace: request.params.ns, + namespace: request.params.ns || request.params.gatewayId, roles: JSON.stringify(scopesToRoles(identityProvider, _scopes)), scopes: _scopes, userId: null, diff --git a/src/controllers/v2/NamespaceController.ts b/src/controllers/v2/NamespaceController.ts index 1ef0715d6..fef567950 100644 --- a/src/controllers/v2/NamespaceController.ts +++ b/src/controllers/v2/NamespaceController.ts @@ -165,7 +165,7 @@ export class NamespaceController extends Controller { result.errors.forEach((err: any, ind: number) => { errors[`d${ind}`] = { message: err.message }; }); - throw new ValidateError(errors, 'Unable to create namespace'); + throw new ValidateError(errors, 'Unable to create gateway'); } return { name: result.data.createNamespace.name, @@ -201,7 +201,7 @@ export class NamespaceController extends Controller { result.errors.forEach((err: any, ind: number) => { errors[`d${ind}`] = { message: err.message }; }); - throw new ValidateError(errors, 'Unable to delete namespace'); + throw new ValidateError(errors, 'Unable to delete gateway'); } return result.data.forceDeleteNamespace; } diff --git a/src/controllers/v2/openapi.yaml b/src/controllers/v2/openapi.yaml index 6696f64e2..50002bffb 100644 --- a/src/controllers/v2/openapi.yaml +++ b/src/controllers/v2/openapi.yaml @@ -71,6 +71,34 @@ components: tags: - tag1 - tag2 + DatasetContact: + properties: + name: + type: string + email: + type: string + role: + type: string + enum: + - pointOfContact + nullable: false + type: object + additionalProperties: false + DatasetResource: + properties: + id: + type: string + name: + type: string + format: + type: string + enum: + - openapi-json + - json + url: + type: string + type: object + additionalProperties: false OrganizationRefID: type: string OrganizationUnitRefID: @@ -100,7 +128,13 @@ components: isDraft: type: string contacts: - type: string + items: + $ref: '#/components/schemas/DatasetContact' + type: array + resources: + items: + $ref: '#/components/schemas/DatasetResource' + type: array extSource: type: string extRecordHash: @@ -109,7 +143,6 @@ components: items: type: string type: array - resources: {} organization: $ref: '#/components/schemas/OrganizationRefID' organizationUnit: @@ -161,9 +194,13 @@ components: isDraft: type: boolean contacts: - type: string + items: + $ref: '#/components/schemas/DatasetContact' + type: array resources: - type: string + items: + $ref: '#/components/schemas/DatasetResource' + type: array tags: items: type: string diff --git a/src/controllers/v2/routes.ts b/src/controllers/v2/routes.ts index 4d6561ced..d141d7077 100644 --- a/src/controllers/v2/routes.ts +++ b/src/controllers/v2/routes.ts @@ -73,6 +73,27 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DatasetContact": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "email": {"dataType":"string"}, + "role": {"dataType":"enum","enums":["pointOfContact"]}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DatasetResource": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string"}, + "name": {"dataType":"string"}, + "format": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["openapi-json"]},{"dataType":"enum","enums":["json"]}]}, + "url": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "OrganizationRefID": { "dataType": "refAlias", "type": {"dataType":"string","validators":{}}, @@ -97,11 +118,11 @@ const models: TsoaRoute.Models = { "title": {"dataType":"string"}, "isInCatalog": {"dataType":"string"}, "isDraft": {"dataType":"string"}, - "contacts": {"dataType":"string"}, + "contacts": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetContact"}}, + "resources": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetResource"}}, "extSource": {"dataType":"string"}, "extRecordHash": {"dataType":"string"}, "tags": {"dataType":"array","array":{"dataType":"string"}}, - "resources": {"dataType":"any"}, "organization": {"ref":"OrganizationRefID"}, "organizationUnit": {"ref":"OrganizationUnitRefID"}, }, @@ -121,8 +142,8 @@ const models: TsoaRoute.Models = { "title": {"dataType":"string"}, "isInCatalog": {"dataType":"boolean"}, "isDraft": {"dataType":"boolean"}, - "contacts": {"dataType":"string"}, - "resources": {"dataType":"string"}, + "contacts": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetContact"}}, + "resources": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetResource"}}, "tags": {"dataType":"array","array":{"dataType":"string"}}, "organization": {"ref":"OrganizationRefID"}, "organizationUnit": {"ref":"OrganizationUnitRefID"}, diff --git a/src/controllers/v2/types.ts b/src/controllers/v2/types.ts index a5ed8e61e..c5909c0cc 100644 --- a/src/controllers/v2/types.ts +++ b/src/controllers/v2/types.ts @@ -56,11 +56,11 @@ export interface Dataset { title?: string; isInCatalog?: string; isDraft?: string; - contacts?: string; + contacts?: DatasetContact[]; + resources?: DatasetResource[]; extSource?: string; extRecordHash?: string; tags?: string[]; - resources?: any; // toString organization?: OrganizationRefID; organizationUnit?: OrganizationUnitRefID; } @@ -96,8 +96,8 @@ export interface DraftDataset { title?: string; isInCatalog?: boolean; isDraft?: boolean; - contacts?: string; - resources?: string; + contacts?: DatasetContact[]; + resources?: DatasetResource[]; tags?: string[]; organization?: OrganizationRefID; organizationUnit?: OrganizationUnitRefID; @@ -138,6 +138,16 @@ export interface Namespace { } +/** + * @tsoaModel + * + */ +export interface Gateway { + gatewayId?: string; // Primary Key + displayName?: string; +} + + /** * @tsoaModel * @@ -503,6 +513,29 @@ export interface Blob { blob?: string; } + +/** + * @tsoaModel + * + */ +export interface DatasetContact { + name?: string; // Primary Key + email?: string; + role?: "pointOfContact"; +} + + +/** + * @tsoaModel + * + */ +export interface DatasetResource { + id?: string; // Primary Key + name?: string; + format?: "openapi-json" | "json"; + url?: string; +} + /** * @tsoaModel */ diff --git a/src/controllers/v3/DatasetController.ts b/src/controllers/v3/DatasetController.ts new file mode 100644 index 000000000..57ff6a119 --- /dev/null +++ b/src/controllers/v3/DatasetController.ts @@ -0,0 +1,105 @@ +import { + Body, + Controller, + OperationId, + Request, + Put, + Path, + Route, + Security, + Tags, + Get, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { + getRecord, + parseJsonString, + removeEmpty, + removeKeys, + syncRecordsThrowErrors, + transformAllRefID, +} from '../../batch/feed-worker'; +import { Dataset, DraftDataset } from './types'; +import { transformContacts, transformResources } from './OrgDatasetController'; +import { BatchResult } from '../../batch/types'; +import { transform } from './DirectoryController'; +import { gql } from 'graphql-request'; +import { Product } from '@/services/keystone/types'; + +@injectable() +@Route('/gateways/{gatewayId}/datasets') +@Security('jwt', ['Namespace.Manage']) +@Tags('API Directory (Administration)') +export class DatasetController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + /** + * Update metadata about a Dataset + * > `Required Scope:` Namespace.Manage + * + * @summary Update Dataset + */ + @Put() + @OperationId('put-dataset') + public async put( + @Path() gatewayId: string, + @Body() body: DraftDataset, + @Request() request: any + ): Promise { + // rules: + // - isInDraft can not be changed (only by Organization) + // - isInCatalog must be false (OrgDataset should only be updating this) + removeKeys(body, ['isInDraft', 'isInCatalog']); + + return await syncRecordsThrowErrors( + this.keystone.createContext(request), + 'DraftDataset', + request.body['name'], + request.body + ); + } + + /** + * Get metadata about a Dataset + * > `Required Scope:` Namespace.Manage + * + * @summary Get Dataset + */ + @Get('{name}') + @OperationId('get-dataset') + public async getDataset( + @Path() gatewayId: string, + @Path() name: string, + @Request() request: any + ): Promise { + const ctx = this.keystone.createContext(request); + + const record = await getRecord(ctx, 'DraftDataset', name, [ + 'organization', + 'organizationUnit', + ]); + + return [record] + .map((o) => removeEmpty(o)) + .map((o) => transformAllRefID(o, [])) + .map((o) => parseJsonString(o, ['tags', 'contacts', 'resources'])) + .map((o) => + removeKeys(o, [ + 'id', + 'namespace', + 'extSource', + 'extForeignKey', + 'extRecordHash', + 'orgUnits', + ]) + ) + .map((o) => transformContacts(o)) + .map((o) => transformResources(o)) + .pop(); + } +} diff --git a/src/controllers/v3/DirectoryController.ts b/src/controllers/v3/DirectoryController.ts new file mode 100644 index 000000000..1d6cf5e53 --- /dev/null +++ b/src/controllers/v3/DirectoryController.ts @@ -0,0 +1,182 @@ +import { Controller, OperationId, Get, Path, Route, Tags } from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { gql } from 'graphql-request'; +import { Product } from '../../services/keystone/types'; +import { + removeEmpty, + removeKeys, + parseJsonString, + transformAllRefID, +} from '../../batch/feed-worker'; + +@injectable() +@Route('/directory') +@Tags('API Directory') +export class DirectoryController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + @Get() + @OperationId('directory-list') + public async list(): Promise { + const result = await this.keystone.executeGraphQL({ + context: this.keystone.sudo(), + query: list, + }); + return transform(result.data.allDiscoverableProducts); + } + + @Get('{id}') + @OperationId('directory-item') + public async get(@Path() id: string): Promise { + const result = await this.keystone.executeGraphQL({ + context: this.keystone.sudo(), + query: item, + variables: { id }, + }); + if (result.data.allDiscoverableProducts.length == 0) { + return null; + } + + return transform( + transformSetAnonymous(result.data.allDiscoverableProducts) + )[0]; + } +} + +export function transformSetAnonymous(products: Product[]) { + products.forEach((prod) => { + prod.environments.forEach((env) => { + env.services.forEach((svc) => { + function setAnonymousIfApplicable(plugins: any[]) { + plugins + ?.filter( + (plugin) => + plugin.name == 'key-auth' || plugin.name == 'jwt-keycloak' + ) + .forEach((plugin) => { + const config = JSON.parse(plugin.config); + if (config.anonymous) { + (env as any).anonymous = true; + } + }); + } + setAnonymousIfApplicable(svc.plugins); + svc.routes?.forEach((route) => { + setAnonymousIfApplicable(route.plugins); + }); + }); + }); + }); + return products; +} + +export function transform(products: Product[]) { + const records: Product[] = products.reduce((accumulator: any, prod: any) => { + if (prod.dataset === null) { + // drop it + } else { + const dataset = accumulator.filter( + (a: any) => a.name === prod.dataset?.name + ); + if (dataset.length == 0) { + accumulator.push(prod.dataset); + prod.dataset.products = [{ ...prod, dataset: null }]; + } else { + dataset[0].products.push({ ...prod, dataset: null }); + } + } + return accumulator; + }, []); + + return records + .map((o) => removeEmpty(o)) + .map((o) => parseJsonString(o, ['tags'])); +} + +const list = gql` + query Directory { + allDiscoverableProducts(where: { environments_some: { active: true } }) { + id + name + environments { + name + active + flow + } + dataset { + id + name + title + notes + license_title + view_audience + security_class + record_publish_date + tags + organization { + name + title + } + organizationUnit { + name + title + } + } + } + } +`; + +const item = gql` + query GetProduct($id: ID!) { + allDiscoverableProducts( + where: { environments_some: { active: true }, dataset: { id: $id } } + ) { + id + name + environments { + name + active + flow + services { + name + host + plugins { + name + tags + config + } + routes { + plugins { + name + config + } + } + } + } + dataset { + name + title + notes + license_title + security_class + view_audience + tags + record_publish_date + isInCatalog + organization { + name + title + } + organizationUnit { + name + title + } + } + } + } +`; diff --git a/src/controllers/v3/EndpointsController.ts b/src/controllers/v3/EndpointsController.ts new file mode 100644 index 000000000..cd7a36a1b --- /dev/null +++ b/src/controllers/v3/EndpointsController.ts @@ -0,0 +1,95 @@ +import { + Controller, + OperationId, + Get, + Route, + Tags, + Query, + Request, + Security, + Path, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { + getRecords, + parseJsonString, + removeEmpty, +} from '../../batch/feed-worker'; +import { GatewayRoute, GatewayService } from './types'; + +interface MatchList { + serviceName: string; + names: string[]; + hosts: string[]; +} + +@injectable() +@Route('/routes') +@Tags('Service Routes') +export class EndpointsController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + @Get('availability') + @OperationId('check-availability') + public async check( + @Query() serviceName: string, + @Query() gatewayId: string, + @Request() request: any + ): Promise { + const ctx = this.keystone.sudo(); + const records = ( + await getRecords(ctx, 'GatewayRoute', 'allGatewayRoutes', ['service']) + ).map((o) => parseJsonString(o, ['hosts'])); + + let counter = 0; + let matchHostList: MatchList; + do { + counter++; + matchHostList = this.getMatchHostList( + counter == 1 + ? serviceName + : counter == 2 + ? `${gatewayId}-${serviceName}` + : `${gatewayId}-${counter}-${serviceName}` + ); + } while (this.isTaken(records, matchHostList)); + + return { + available: counter == 1, + suggestion: matchHostList, + }; + } + + private getMatchHostList(serviceName: string): MatchList { + return { + serviceName, + names: [`${serviceName}`, `${serviceName}-dev`, `${serviceName}-test`], + hosts: [ + `${serviceName}.api.gov.bc.ca`, + `${serviceName}.dev.api.gov.bc.ca`, + `${serviceName}.test.api.gov.bc.ca`, + `${serviceName}-api-gov-bc-ca.dev.api.gov.bc.ca`, + `${serviceName}-api-gov-bc-ca.test.api.gov.bc.ca`, + ], + }; + } + + private isTaken(records: any[], matchList: MatchList): boolean { + return ( + records.filter( + (r: GatewayRoute) => + r.hosts.filter((h: string) => matchList.hosts.indexOf(h) >= 0) + .length > 0 || + matchList.names.filter((n: string) => n == r.name).length > 0 || + matchList.names.filter( + (n: string) => n == (r.service as GatewayService).name + ).length > 0 + ).length > 0 + ); + } +} diff --git a/src/controllers/v3/GatewayController.ts b/src/controllers/v3/GatewayController.ts new file mode 100644 index 000000000..20e208481 --- /dev/null +++ b/src/controllers/v3/GatewayController.ts @@ -0,0 +1,327 @@ +import { + Controller, + OperationId, + Request, + Get, + Path, + Route, + Security, + Tags, + Delete, + Query, + Post, + Body, +} from 'tsoa'; +import { ValidateError, FieldErrors } from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { getRecords, replaceKey } from '../../batch/feed-worker'; +import { gql } from 'graphql-request'; +import { WorkbookService } from '../../services/report/workbook.service'; +import { Namespace, NamespaceInput } from '../../services/keystone/types'; + +import { Readable } from 'stream'; +import { + parseBlobString, + parseJsonString, + removeEmpty, + removeKeys, + transformAllRefID, +} from '../../batch/feed-worker'; + +import { strict as assert } from 'assert'; + +import { Logger } from '../../logger'; +import { Activity, Gateway, GatewayRoute } from './types'; +import { getActivity } from '../../services/keystone/activity'; +import { transformActivity } from '../../services/workflow'; +import { ActivityDetail } from './types-extra'; + +const logger = Logger('controllers.Gateway'); + +/** + * @param binary Buffer + * returns readableInstanceStream Readable + */ +function bufferToStream(binary: any) { + const readableInstanceStream = new Readable({ + read() { + this.push(binary); + this.push(null); + }, + }); + + return readableInstanceStream; +} + +@injectable() +@Route('/gateways') +@Security('jwt') +@Tags('Gateways') +export class NamespaceController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + @Get('/report') + @OperationId('report') + public async report( + @Request() req: any, + @Query() ids: string = '[]' + ): Promise { + const workbookService = new WorkbookService( + this.keystone.createContext(req, true) + ); + const workbook = await workbookService.buildWorkbook(JSON.parse(ids)); + const buffer = await workbook.xlsx.writeBuffer(); + + req.res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ); + req.res.setHeader( + 'Content-Disposition', + 'attachment; filename="bcgov_app_gateways.xlsx"' + ); + + const mystream = bufferToStream(buffer); + mystream.pipe(req.res); + await new Promise((resolve, reject) => { + mystream.on('end', () => { + req.res.end(); + resolve(null); + }); + }); + + return null; + } + + /** + * @summary List of Gateways available to the user + * @param request + * @returns + */ + @Get() + @OperationId('gateway-list') + public async list(@Request() request: any): Promise { + const result = await this.keystone.executeGraphQL({ + context: this.keystone.createContext(request), + query: list, + }); + logger.debug('Result %j', result); + return result.data.allNamespaces + .map((ns: Namespace): Gateway => ({ gatewayId: ns.name, displayName: ns.displayName })) + .sort((a: Gateway, b: Gateway) => { + const displayNameComparison = a.displayName.localeCompare(b.displayName); + return displayNameComparison !== 0 ? displayNameComparison : a.gatewayId.localeCompare(b.gatewayId); + }); + } + + /** + * Get details about the gateway, such as permissions for what the gateway is setup with. + * > `Required Scope:` Namespace.Manage + * + * @summary Gateway Summary + * @param ns + * @param request + * @returns + */ + @Get('/{gatewayId}') + @OperationId('namespace-profile') + @Security('jwt', ['Namespace.Manage']) + public async profile( + @Path() gatewayId: string, + @Request() request: any + ): Promise { + const result = await this.keystone.executeGraphQL({ + context: this.keystone.createContext(request), + query: item, + variables: { ns: gatewayId }, + }); + logger.debug('Result %j', result); + assert.strictEqual('errors' in result, false, 'Unable to process request'); + return result.data.namespace; + } + + /** + * Create a gateway + * + * @summary Create Gateway + * @param ns + * @param request + * @returns + */ + @Post() + @OperationId('create-gateway') + @Security('jwt', []) + public async create( + @Request() request: any, + @Body() vars: Gateway + ): Promise { + logger.debug('Input %j', vars); + const modifiedVars = replaceKey(vars, 'gatewayId', 'name'); + const result = await this.keystone.executeGraphQL({ + context: this.keystone.createContext(request), + query: createNS, + variables: modifiedVars, + }); + logger.debug('Result %j', result); + if (result.errors) { + const errors: FieldErrors = {}; + result.errors.forEach((err: any, ind: number) => { + errors[`d${ind}`] = { message: err.message }; + }); + logger.error('%j', result); + throw new ValidateError(errors, 'Unable to create namespace'); + } + return { + gatewayId: result.data.createNamespace.name, + displayName: result.data.createNamespace.displayName, + }; + } + + /** + * Delete the gateway + * > `Required Scope:` Namespace.Manage + * + * @summary Delete Gateway + * @param ns + * @param request + * @returns + */ + @Delete('/{gatewayId}') + @OperationId('delete-namespace') + @Security('jwt', ['Namespace.Manage']) + public async delete( + @Path() gatewayId: string, + @Query() force: boolean = false, + @Request() request: any + ): Promise { + const ns = gatewayId; + const result = await this.keystone.executeGraphQL({ + context: this.keystone.createContext(request), + query: deleteNS, + variables: { ns, force }, + }); + logger.debug('Result %j', result); + if (result.errors) { + const errors: FieldErrors = {}; + result.errors.forEach((err: any, ind: number) => { + errors[`d${ind}`] = { message: err.message }; + }); + logger.error('%j', result); + throw new ValidateError(errors, 'Unable to delete gateway'); + } + return result.data.forceDeleteNamespace; + } + + /** + * > `Required Scope:` Namespace.View + * + * @summary Get administration activity for this gateway + * @param ns + * @param first + * @param skip + * @returns Activity[] + */ + @Get('/{gatewayId}/activity') + @OperationId('gateway-admin-activity') + @Security('jwt', ['Namespace.View']) + public async namespaceActivity( + @Path() gatewayId: string, + @Query() first: number = 20, + @Query() skip: number = 0 + ): Promise { + const ctx = this.keystone.sudo(); + const records = await getActivity( + ctx, + [gatewayId], + undefined, + first > 100 ? 100 : first, + skip + ); + return transformActivity(records) + .map((o) => removeKeys(o, ['id'])) + .map((o) => removeEmpty(o)) + .map((o) => parseJsonString(o, ['context'])) + .map((o) => parseBlobString(o)); + } + + /** + * Get a summary of your endpoints + * > `Required Scope:` Namespace.Manage + * + * @summary Get endpoints + */ + @Get('/{gatewayId}/links') + @OperationId('get-gateway-links') + @Security('jwt', ['Namespace.Manage']) + public async get( + @Path() gatewayId: string, + @Request() request: any + ): Promise<{ host: string }[]> { + const ctx = this.keystone.createContext(request); + const records = await getRecords( + ctx, + 'GatewayRoute', + 'allGatewayRoutesByNamespace', + [] + ); + + const endpoints: string[] = []; + records.forEach((r: GatewayRoute) => + r.hosts.forEach((h: string) => endpoints.push(h)) + ); + + return [...new Set(endpoints)].map((host) => ({ + type: 'route-host', + host: `https://${host}`, + })); + } +} + +const list = gql` + query Namespaces { + allNamespaces { + name + displayName + } + } +`; + +const item = gql` + query Namespace($ns: String!) { + namespace(ns: $ns) { + name + displayName + scopes { + name + } + permDomains + permDataPlane + permProtectedNs + org + orgUnit + orgUpdatedAt + orgEnabled + orgAdmins + } + } +`; + +const deleteNS = gql` + mutation ForceDeleteNamespace($ns: String!, $force: Boolean!) { + forceDeleteNamespace(namespace: $ns, force: $force) + } +`; + +const createNS = gql` + mutation CreateNamespace($name: String, $displayName: String) { + createNamespace(name: $name, displayName: $displayName) { + name + displayName + } + } +`; diff --git a/src/controllers/v3/GatewayDirectoryController.ts b/src/controllers/v3/GatewayDirectoryController.ts new file mode 100644 index 000000000..a5de7ce90 --- /dev/null +++ b/src/controllers/v3/GatewayDirectoryController.ts @@ -0,0 +1,178 @@ +import { + Controller, + OperationId, + Request, + Path, + Route, + Security, + Tags, + Get, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { transform, transformSetAnonymous } from './DirectoryController'; +import { gql } from 'graphql-request'; +import { strict as assert } from 'assert'; + +@injectable() +@Route('/gateways/{gatewayId}/directory') +@Security('jwt', ['Namespace.Manage']) +@Tags('API Directory (Administration)') +export class GatewayDirectoryController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + /** + * Used primarily for "Preview Mode" + * Get a particular Dataset + * + * @param ns + * @param name + * @param request + * @returns + */ + @Get('{id}') + @OperationId('get-ns-directory-dataset') + public async getDataset( + @Path() gatewayId: string, + @Path() id: string, + @Request() request: any + ): Promise { + const context = this.keystone.createContext(request); + const result = await this.keystone.executeGraphQL({ + context, + query: item, + variables: { id }, + }); + + assert.strictEqual( + result.data.allProductsByNamespace?.length == 0, + false, + 'No products for dataset found' + ); + + return transform( + transformSetAnonymous(result.data.allProductsByNamespace) + )[0]; + } + + /** + * Used primarily for "Preview Mode" + * List the datasets belonging to a particular namespace + * + * @param ns + * @param name + * @param request + * @returns + */ + @Get() + @OperationId('get-ns-directory') + public async getDatasets( + @Path() gatewayId: string, + @Request() request: any + ): Promise { + const context = this.keystone.createContext(request); + const result = await this.keystone.executeGraphQL({ + context, + query: list, + }); + // For Preview, put a placeholder Dataset so that it gets returned + // result.data.allProductsByNamespace + // .filter((prod: Product) => !prod.dataset) + // .forEach((prod: Product) => { + // prod.dataset = { + // id: '--', + // name: 'Placeholder Dataset', + // title: 'Placeholder Dataset', + // isInCatalog: false, + // isDraft: true, + // }; + // }); + + return transform(result.data.allProductsByNamespace); + } +} + +const list = gql` + query DirectoryNamespacePreview { + allProductsByNamespace { + id + name + environments { + name + active + flow + } + dataset { + id + name + title + notes + license_title + view_audience + security_class + record_publish_date + tags + organization { + name + title + } + organizationUnit { + name + title + } + } + } + } +`; + +const item = gql` + query DirectoryNamespaceDataset($id: ID!) { + allProductsByNamespace(where: { dataset: { id: $id } }) { + id + name + environments { + name + active + flow + services { + name + host + plugins { + name + tags + config + } + routes { + plugins { + name + config + } + } + } + } + dataset { + name + title + notes + license_title + security_class + view_audience + tags + record_publish_date + isInCatalog + organization { + name + title + } + organizationUnit { + name + title + } + } + } + } +`; diff --git a/src/controllers/v3/GatewayServicesController.ts b/src/controllers/v3/GatewayServicesController.ts new file mode 100644 index 000000000..801e5837b --- /dev/null +++ b/src/controllers/v3/GatewayServicesController.ts @@ -0,0 +1,84 @@ +import { + Controller, + Request, + OperationId, + Get, + Put, + Path, + Route, + Security, + Body, + Tags, + FormField, + UploadedFile, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { + syncRecords, + getRecords, + parseJsonString, + removeEmpty, + removeKeys, +} from '../../batch/feed-worker'; +import { GatewayRoute } from './types'; +import { PublishResult } from './types-extra'; + +@injectable() +@Route('/gateways/{gatewayId}/services') +@Tags('Gateway Services') +export class GatewayController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + @Put() + @OperationId('publish-gateway-config') + @Security('jwt', ['Gateway.Config']) + public async put( + @FormField() dryRun: boolean, + @UploadedFile() configFile: Express.Multer.File + ): Promise { + // stub - gwa-api implements this + return { error: 'Stub - not implemented' }; + } + + /** + * Get a summary of your Gateway Services + * > `Required Scope:` Namespace.Manage + * + * @summary Get Gateway Services + */ + @Get() + @OperationId('get-gateway-routes') + @Security('jwt', ['Namespace.Manage']) + public async getServices( + @Path() gatewayId: string, + @Request() request: any + ): Promise { + const ctx = this.keystone.createContext(request); + const records = await getRecords( + ctx, + 'GatewayRoute', + 'allGatewayRoutesByNamespace', + ['plugins', 'service'] + ); + + return records + .map((o) => removeEmpty(o)) + .map((o) => + parseJsonString(o, ['tags', 'config', 'paths', 'hosts', 'methods']) + ) + .map((o) => + removeKeys(o, [ + 'id', + 'namespace', + 'extSource', + 'extRecordHash', + 'extForeignKey', + ]) + ); + } +} diff --git a/src/controllers/v3/IdentifierController.ts b/src/controllers/v3/IdentifierController.ts new file mode 100644 index 000000000..7bed441d4 --- /dev/null +++ b/src/controllers/v3/IdentifierController.ts @@ -0,0 +1,29 @@ +import { Controller, Get, Path, Route, Tags } from 'tsoa'; + +import { + newProductID, + newEnvironmentID, + newApplicationID, + newGatewayID, +} from '../../services/identifiers'; + +@Route('identifiers') +@Tags('New Identifiers') +export class IdentifiersController extends Controller { + @Get('{type}') + public async getNewID( + @Path() type: 'environment' | 'product' | 'application' | 'gateway' + ): Promise { + if (type == 'environment') { + return newEnvironmentID(); + } else if (type == 'gateway') { + return newGatewayID(); + } else if (type == 'product') { + return newProductID(); + } else if (type == 'application') { + return newApplicationID(); + } else { + return ''; + } + } +} diff --git a/src/controllers/v3/IssuerController.ts b/src/controllers/v3/IssuerController.ts new file mode 100644 index 000000000..98598932c --- /dev/null +++ b/src/controllers/v3/IssuerController.ts @@ -0,0 +1,128 @@ +import { + Controller, + Request, + Delete, + Get, + OperationId, + Put, + Path, + Route, + Security, + Body, + Tags, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { + syncRecordsThrowErrors, + getRecords, + removeEmpty, + removeKeys, + parseJsonString, + transformAllRefID, + getRecord, + deleteRecord, +} from '../../batch/feed-worker'; +import { CredentialIssuer } from './types'; +import { BatchResult } from '../../batch/types'; +import { strict as assert } from 'assert'; + +@injectable() +@Route('/gateways/{gatewayId}/issuers') +@Tags('Authorization Profiles') +export class IssuerController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + /** + * Create or Update Authorization Profiles + * > `Required Scope:` CredentialIssuer.Admin + * + * @summary Manage Authorization Profiles + */ + @Put() + @OperationId('put-issuer') + @Security('jwt', ['CredentialIssuer.Admin']) + public async put( + @Path() gatewayId: string, + @Body() body: CredentialIssuer, + @Request() request: any + ): Promise { + return await syncRecordsThrowErrors( + this.keystone.createContext(request), + 'CredentialIssuer', + body['name'], + body + ); + } + + /** + * Get Authorization Profiles setup in this namespace + * > `Required Scope:` Namespace.Manage + * + * @summary Get Authorization Profiles + */ + @Get() + @OperationId('get-issuers') + @Security('jwt', ['Namespace.Manage']) + public async get( + @Path() gatewayId: string, + @Request() request: any + ): Promise { + const ctx = this.keystone.createContext(request); + const records = await getRecords( + ctx, + 'CredentialIssuer', + 'allCredentialIssuersByNamespace', + [] + ); + + return records + .map((o) => removeEmpty(o)) + .map((o) => transformAllRefID(o, ['owner'])) + .map((o) => + parseJsonString(o, [ + 'availableScopes', + 'resourceScopes', + 'clientRoles', + 'clientMappers', + 'environmentDetails', + ]) + ) + .map((o) => + removeKeys(o, [ + 'id', + 'namespace', + 'extSource', + 'extForeignKey', + 'extRecordHash', + ]) + ); + } + + /** + * Delete an Authorization Profile + * > `Required Scope:` CredentialIssuer.Admin + * + * @summary Delete Profile + */ + @Delete('/{name}') + @OperationId('delete-issuer') + @Security('jwt', ['CredentialIssuer.Admin']) + public async delete( + @Path() gatewayId: string, + @Path() name: string, + @Request() request: any + ): Promise { + const context = this.keystone.createContext(request); + + const current = await getRecord(context, 'CredentialIssuer', name); + assert.strictEqual(current.namespace === gatewayId, true, 'Issuer invalid'); + assert.strictEqual(current === null, false, 'Issuer not found'); + + return await deleteRecord(context, 'CredentialIssuer', name); + } +} diff --git a/src/controllers/v3/OrgDatasetController.ts b/src/controllers/v3/OrgDatasetController.ts new file mode 100644 index 000000000..80e9b3219 --- /dev/null +++ b/src/controllers/v3/OrgDatasetController.ts @@ -0,0 +1,208 @@ +import { + Controller, + Request, + OperationId, + Put, + Path, + Route, + Security, + Body, + Get, + Tags, + Delete, +} from 'tsoa'; +import { strict as assert } from 'assert'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { + syncRecordsThrowErrors, + getRecords, + parseJsonString, + removeEmpty, + removeKeys, + transformAllRefID, + deleteRecord, +} from '../../batch/feed-worker'; +import { BatchResult } from '../../batch/types'; +import { Dataset, DraftDataset } from './types'; + +@injectable() +@Route('/organizations') +@Tags('API Directory (Administration)') +export class OrgDatasetController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + /** + * Get metadata about Datasets that are available by API for this organization + * > `Required Scope:` Dataset.Manage + * + * @summary Get Organization Datasets + */ + @Get('/{org}/datasets') + @OperationId('organization-datasets') + @Security('jwt', ['Dataset.Manage']) + public async getDatasets( + @Path() org: string, + @Request() request: any + ): Promise { + const ctx = this.keystone.createContext(request); + + const batchClause = { + query: '$org: String', + clause: '{ organization: { name: $org } }', + variables: { org }, + }; + + const records = await getRecords( + ctx, + 'DraftDataset', + undefined, + [], + batchClause + ); + + return records + .map((o) => removeEmpty(o)) + .map((o) => transformAllRefID(o, ['organization', 'organizationUnit'])) + .map((o) => parseJsonString(o, ['tags'])) + .map((o) => + removeKeys(o, [ + 'id', + 'namespace', + 'extSource', + 'extRecordHash', + 'extForeignKey', + ]) + ); + } + + /** + * Manage metadata about Datasets that are available by API for this organization + * > `Required Scope:` Dataset.Manage + * + * @summary Manage Organization Datasets + */ + @Put('{org}/datasets') + @OperationId('put-organization-dataset') + @Security('jwt', ['Dataset.Manage']) + public async putDataset( + @Path() org: string, + @Body() body: DraftDataset, + @Request() request: any + ): Promise { + assert.strictEqual(org, body['organization'], 'Organization Mismatch'); + return await syncRecordsThrowErrors( + this.keystone.createContext(request, true), + 'DraftDataset', + body['name'], + body + ); + } + + /** + * Delete a Dataset + * > `Required Scope:` Dataset.Manage + * + * @summary Delete a dataset + * @param ns + * @param appId + * @param request + * @returns + */ + @Delete('/{org}/datasets/{name}') + @OperationId('delete-dataset') + @Security('jwt', ['Dataset.Manage']) + public async delete( + @Path() org: string, + @Path() name: string, + @Request() request: any + ): Promise { + const context = this.keystone.createContext(request, true); + + const records = await getRecords( + context, + 'DraftDataset', + undefined, + ['organization', 'organizationUnit'], + { + query: '$org: String!, $name: String!', + clause: '{ organization: { name: $org }, name: $name }', + variables: { org, name }, + } + ); + + assert.strictEqual(records.length == 0, false, 'Dataset not found'); + + return await deleteRecord(context, 'DraftDataset', records.pop().name); + } + + /** + * Get metadata about a Dataset that are available by API for this organization + * > `Required Scope:` Dataset.Manage + * + * @summary Get Organization Dataset + */ + @Get('/{org}/datasets/{name}') + @OperationId('get-organization-dataset') + @Security('jwt', ['Dataset.Manage']) + public async getDataset( + @Path() org: string, + @Path() name: string, + @Request() request: any + ): Promise { + const ctx = this.keystone.createContext(request); + + const records = await getRecords( + ctx, + 'DraftDataset', + undefined, + ['organization', 'organizationUnit'], + { + query: '$org: String!, $name: String!', + clause: '{ organization: { name: $org }, name: $name }', + variables: { org, name }, + } + ); + + return records + .map((o) => removeEmpty(o)) + .map((o) => transformAllRefID(o, [])) + .map((o) => parseJsonString(o, ['tags', 'contacts', 'resources'])) + .map((o) => + removeKeys(o, [ + 'id', + 'namespace', + 'extSource', + 'extForeignKey', + 'extRecordHash', + 'orgUnits', + ]) + ) + .map((o) => transformContacts(o)) + .map((o) => transformResources(o)) + .pop(); + } +} + +export function transformResources(o: any) { + o.resources = o.resources?.map((res: any) => ({ + name: res.name, + url: res.url, + bcdc_type: res.bcdc_type, + format: res.format, + })); + return o; +} + +export function transformContacts(o: any) { + o.contacts = o.contacts?.map((con: any) => ({ + role: con.role, + name: con.name, + email: con.email, + })); + return o; +} diff --git a/src/controllers/v3/OrgRoleController.ts b/src/controllers/v3/OrgRoleController.ts new file mode 100644 index 000000000..faaece205 --- /dev/null +++ b/src/controllers/v3/OrgRoleController.ts @@ -0,0 +1,12 @@ +import { Controller, Get, Path, Route, Tags } from 'tsoa'; + +import { PredefinedRolePermissions } from '../../services/org-groups'; + +@Route('roles') +@Tags('Organizations') +export class OrgRoleController extends Controller { + @Get() + public async getRoles(): Promise { + return PredefinedRolePermissions; + } +} diff --git a/src/controllers/v3/OrganizationController.ts b/src/controllers/v3/OrganizationController.ts new file mode 100644 index 000000000..a4d904cfc --- /dev/null +++ b/src/controllers/v3/OrganizationController.ts @@ -0,0 +1,308 @@ +import { + Controller, + Request, + Delete, + OperationId, + Put, + Path, + Route, + Query, + Security, + Body, + Get, + Tags, + Post, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { + syncRecords, + getRecords, + parseJsonString, + removeEmpty, + removeKeys, + transformAllRefID, + syncRecordsThrowErrors, + parseBlobString, +} from '../../batch/feed-worker'; +import { + GroupAccessService, + leaf, + NamespaceService, +} from '../../services/org-groups'; +import { + getGwaProductEnvironment, + transformActivity, +} from '../../services/workflow'; +import { + GroupAccess, + GroupMembership, + OrgNamespace, +} from '../../services/org-groups/types'; +import { getOrganizations, getOrganizationUnit } from '../../services/keystone'; +import { getActivity } from '../../services/keystone/activity'; +import { Activity, Organization } from './types'; +import { isParent } from '../../services/org-groups/group-converter-utils'; +import { ActivitySummary } from '../../services/keystone/types'; +import { ActivityDetail } from './types-extra'; +import { BatchResult } from '../../batch/types'; +import { assertEqual } from '../ioc/assert'; + +@injectable() +@Route('/organizations') +@Tags('Organizations') +export class OrganizationController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + @Get() + @OperationId('organization-list') + public async listOrganizations(): Promise { + const orgs = await getOrganizations(this.keystone.sudo()); + return orgs.map((o) => ({ + name: o.name, + title: o.title, + description: o.description, + })); + } + + /** + * Create Organization + * > `Required Scope:` GroupAccess.Manage + * + * @summary Create Organizations + * @param ns + * @param body + * @param request + */ + @Put('{org}') + @OperationId('put-organization') + @Security('jwt', ['GroupAccess.Manage']) + public async post( + @Path() org: string, + @Body() body: Organization, + @Request() request: any + ): Promise { + assertEqual( + org == 'ca.bc.gov', + true, + 'org', + 'Only root level is allowed to do this operation' + ); + return await syncRecordsThrowErrors( + this.keystone.createContext(request, true), + 'Organization', + body['name'], + body + ); + } + + @Get('{org}') + @OperationId('organization-units') + public async listOrganizationUnits(@Path() org: string): Promise { + const orgs = await getOrganizations(this.keystone.sudo()); + const match = orgs.filter((o) => o.name === org).pop(); + assertEqual( + typeof match === 'undefined', + false, + 'org', + 'Organization not found.' + ); + + return { + orgUnits: match.orgUnits.map((o) => ({ + name: o.name, + title: o.title, + description: o.description, + })), + }; + } + + /** + * > `Required Scope:` GroupAccess.Manage + */ + @Get('{org}/roles') + @OperationId('get-organization-roles') + @Security('jwt', ['GroupAccess.Manage']) + public async getPolicies(@Path() org: string): Promise { + const prodEnv = await getGwaProductEnvironment(this.keystone.sudo(), false); + const envConfig = prodEnv.issuerEnvConfig; + + const groupAccessService = new GroupAccessService(prodEnv.uma2); + await groupAccessService.login(envConfig.clientId, envConfig.clientSecret); + + return await groupAccessService.getGroupAccess(org); + } + + /** + * > `Required Scope:` GroupAccess.Manage + */ + @Get('{org}/access') + @OperationId('get-organization-access') + @Security('jwt', ['GroupAccess.Manage']) + public async get(@Path() org: string): Promise { + const prodEnv = await getGwaProductEnvironment(this.keystone.sudo(), false); + const envConfig = prodEnv.issuerEnvConfig; + + const groupAccessService = new GroupAccessService(prodEnv.uma2); + await groupAccessService.login(envConfig.clientId, envConfig.clientSecret); + + return await groupAccessService.getGroupMembership(org); + } + + /** + * > `Required Scope:` GroupAccess.Manage + */ + @Put('{org}/access') + @OperationId('put-organization-access') + @Security('jwt', ['GroupAccess.Manage']) + public async put( + @Path() org: string, + @Body() body: GroupMembership + ): Promise { + // must match either the 'name' or one of the parent nodes + assertEqual( + org === body.name || isParent(body.parent, org), + true, + 'org', + 'Organization mismatch' + ); + + const prodEnv = await getGwaProductEnvironment(this.keystone.sudo(), false); + const envConfig = prodEnv.issuerEnvConfig; + + const groupAccessService = new GroupAccessService(prodEnv.uma2); + await groupAccessService.login(envConfig.clientId, envConfig.clientSecret); + + await groupAccessService.createOrUpdateGroupAccess(body, ['idir']); + } + + /** + * > `Required Scope:` Namespace.Assign + */ + @Get('{org}/gateways') + @OperationId('organization-gateways') + @Security('jwt', ['Namespace.Assign']) + public async listNamespaces(@Path() org: string): Promise { + const prodEnv = await getGwaProductEnvironment(this.keystone.sudo(), false); + const envConfig = prodEnv.issuerEnvConfig; + + const svc = new NamespaceService(envConfig.issuerUrl); + await svc.login(envConfig.clientId, envConfig.clientSecret); + return await svc.listAssignedNamespacesByOrg(org); + } + + /** + * > `Required Scope:` Namespace.Assign + */ + @Put('{org}/{orgUnit}/gateways/{gatewayId}') + @OperationId('assign-namespace-to-organization') + @Security('jwt', ['Namespace.Assign']) + public async assignNamespace( + @Path() org: string, + @Path() orgUnit: string, + @Path() gatewayId: string, + @Query() enable: boolean = true + ): Promise<{ result: string }> { + const ns = gatewayId; + const ctx = this.keystone.sudo(); + const orgLookup = await getOrganizationUnit(ctx, orgUnit); + assertEqual( + orgLookup != null && orgLookup.name === org, + true, + 'org', + 'Invalid Organization' + ); + + const prodEnv = await getGwaProductEnvironment(ctx, false); + const envConfig = prodEnv.issuerEnvConfig; + + const svc = new GroupAccessService(prodEnv.uma2); + await svc.login(envConfig.clientId, envConfig.clientSecret); + const answer = await svc.assignNamespace(ns, org, orgUnit, enable); + return { + result: answer + ? 'namespace-assigned' + : 'no-update-namespace-already-assigned', + }; + } + + /** + * > `Required Scope:` Namespace.Assign + */ + @Delete('{org}/{orgUnit}/gateways/{gatewayId}') + @OperationId('unassign-namespace-from-organization') + @Security('jwt', ['Namespace.Assign']) + public async unassignNamespace( + @Path() org: string, + @Path() orgUnit: string, + @Path() gatewayId: string + ): Promise<{ result: string }> { + const ns = gatewayId; + const ctx = this.keystone.sudo(); + const orgLookup = await getOrganizationUnit(ctx, orgUnit); + assertEqual( + orgLookup != null && orgLookup.name === org, + true, + 'org', + 'Invalid Organization' + ); + + const prodEnv = await getGwaProductEnvironment(ctx, false); + const envConfig = prodEnv.issuerEnvConfig; + + const svc = new GroupAccessService(prodEnv.uma2); + await svc.login(envConfig.clientId, envConfig.clientSecret); + const answer = await svc.unassignNamespace(ns, org, orgUnit); + return { + result: answer + ? 'namespace-unassigned' + : 'no-update-namespace-not-assigned', + }; + } + + /** + * > `Required Scope:` Namespace.Assign + * + * @summary Get administration activity for gateways associated with this Organization + * @param org + * @param first + * @param skip + * @returns Activity[] + */ + @Get('{org}/activity') + @OperationId('org-namespace-activity') + @Security('jwt', ['Namespace.Assign']) + public async namespaceActivity( + @Path() org: string, + @Query() first: number = 20, + @Query() skip: number = 0 + ): Promise { + const ctx = this.keystone.sudo(); + //const org = await getOrganizationUnit(ctx, orgUnit); + //assert.strictEqual(org != null, true, 'Invalid Organization Unit'); + + const prodEnv = await getGwaProductEnvironment(ctx, false); + const envConfig = prodEnv.issuerEnvConfig; + + const svc = new NamespaceService(envConfig.issuerUrl); + await svc.login(envConfig.clientId, envConfig.clientSecret); + const assignedNamespaces = await svc.listAssignedNamespacesByOrg(org); + const records = await getActivity( + ctx, + assignedNamespaces.map((n) => n.name), + undefined, + first > 100 ? 100 : first, + skip + ); + + return transformActivity(records) + .map((o) => removeKeys(o, ['id'])) + .map((o) => removeEmpty(o)) + .map((o) => parseJsonString(o, ['context'])) + .map((o) => parseBlobString(o)); + } +} diff --git a/src/controllers/v3/ProductController.ts b/src/controllers/v3/ProductController.ts new file mode 100644 index 000000000..81a10de66 --- /dev/null +++ b/src/controllers/v3/ProductController.ts @@ -0,0 +1,219 @@ +import { + Controller, + Request, + Delete, + Query, + OperationId, + Get, + Put, + Path, + Route, + Security, + Body, + Tags, + FieldErrors, + ValidateError, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { + syncRecordsThrowErrors, + getRecords, + removeEmpty, + removeKeys, + transformAllRefID, + deleteRecord, + getRecord, + transformArrayKeyToString, + replaceKey, +} from '../../batch/feed-worker'; +import { Product } from './types'; +import { BatchResult } from '../../batch/types'; + +import { Logger } from '../../logger'; +import { gql } from 'graphql-request'; +import { strict as assert } from 'assert'; +import { isEnvironmentID, isProductID } from '../../services/identifiers'; +import { Product as KSProduct } from '../../services/keystone/types'; + +const logger = Logger('controllers.Product'); + +@injectable() +@Route('/gateways/{gatewayId}') +@Tags('Products') +export class ProductController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + /** + * Manage Products for APIs that will appear on the API Directory + * > `Required Scope:` Namespace.Manage + * + * @summary Manage Products + * @param ns + * @param body + * @param request + */ + @Put('/products') + @OperationId('put-product') + @Security('jwt', ['Namespace.Manage']) + public async put( + @Path() gatewayId: string, + @Body() body: Product, + @Request() request: any + ): Promise { + body['gatewayId'] = gatewayId; + return await syncRecordsThrowErrors( + this.keystone.createContext(request), + 'Product', + body['appId'], + replaceKey(body, 'gatewayId', 'namespace') + ); + } + + /** + * Get Products describing APIs that will appear on the API Directory + * > `Required Scope:` Namespace.Manage + * + * @summary Get Products + * @param ns + * @param request + * @returns + */ + @Get('/products') + @OperationId('get-products') + @Security('jwt', ['Namespace.Manage']) + public async get(@Request() request: any): Promise { + const ctx = this.keystone.createContext(request); + const records: KSProduct[] = await getRecords( + ctx, + 'Product', + 'allProductsByNamespace', + ['environments'] + ); + + return records + .map((o) => removeEmpty(o)) + .map((o) => + transformAllRefID(o, ['credentialIssuer', 'dataset', 'legal']) + ) + .map((o) => transformArrayKeyToString(o, 'services', 'name')) + .map((o) => + removeKeys(o, [ + 'id', + 'namespace', + 'product', + 'extSource', + 'extRecordHash', + 'extForeignKey', + ]) + ); + } + + /** + * Delete a Product + * > `Required Scope:` Namespace.Manage + * + * @summary Manage Products + * @param ns + * @param appId + * @param request + * @returns + */ + @Delete('/products/{appId}') + @OperationId('delete-product') + @Security('jwt', ['Namespace.Manage']) + public async delete( + @Path() gatewayId: string, + @Path() appId: string, + @Request() request: any + ): Promise { + const context = this.keystone.createContext(request); + + assert.strictEqual(isProductID(appId), true, 'Invalid appId'); + + const current = await getRecord(context, 'Product', appId); + assert.strictEqual(current === null, false, 'Product not found'); + assert.strictEqual( + current.namespace === gatewayId, + true, + 'Product invalid' + ); + return await deleteRecord(context, 'Product', appId); + } + + /** + * Delete a Product Environment + * > `Required Scope:` Namespace.Manage + * + * @summary Delete a Product Environment + * @param ns + * @param appId + * @param force + * @param request + * @returns + */ + @Delete('/environments/{appId}') + @OperationId('delete-product-environment') + @Security('jwt', ['Namespace.Manage']) + public async deleteEnvironment( + @Path() gatewayId: string, + @Path() appId: string, + @Query() force: boolean = false, + @Request() request: any + ): Promise { + const context = this.keystone.createContext(request); + + assert.strictEqual(isEnvironmentID(appId), true, 'Invalid appId'); + + const records: KSProduct[] = await getRecords( + context, + 'Product', + 'allProductsByNamespace', + ['environments'] + ); + const product = records + .filter((p) => p.environments.filter((e) => e.appId === appId).length > 0) + .pop(); + + assert.strictEqual( + typeof product === 'undefined', + false, + 'Environment not found' + ); + assert.strictEqual( + product.namespace === gatewayId, + true, + 'Environment invalid' + ); + + const environment = product.environments + .filter((e) => e.appId === appId) + .pop(); + + const result = await this.keystone.executeGraphQL({ + context, + query: deleteEnvironment, + variables: { prodEnvId: environment.id, force }, + }); + logger.debug('Result %j', result); + if (result.errors) { + const errors: FieldErrors = {}; + result.errors.forEach((err: any, ind: number) => { + errors[`d${ind}`] = { message: err.message }; + }); + logger.error('%j', result); + throw new ValidateError(errors, 'Unable to delete product environment'); + } + return result.data.forceDeleteEnvironment; + } +} + +const deleteEnvironment = gql` + mutation ForceDeleteEnvironment($prodEnvId: ID!, $force: Boolean!) { + forceDeleteEnvironment(id: $prodEnvId, force: $force) + } +`; diff --git a/src/controllers/v3/openapi.yaml b/src/controllers/v3/openapi.yaml new file mode 100644 index 000000000..ec2d79f47 --- /dev/null +++ b/src/controllers/v3/openapi.yaml @@ -0,0 +1,1722 @@ +components: + examples: {} + headers: {} + parameters: {} + requestBodies: {} + responses: {} + schemas: + DatasetContact: + properties: + name: + type: string + email: + type: string + role: + type: string + enum: + - pointOfContact + nullable: false + type: object + additionalProperties: false + DatasetResource: + properties: + id: + type: string + name: + type: string + format: + type: string + enum: + - openapi-json + - json + url: + type: string + type: object + additionalProperties: false + OrganizationRefID: + type: string + OrganizationUnitRefID: + type: string + Dataset: + properties: + extForeignKey: + type: string + name: + type: string + license_title: + type: string + security_class: + type: string + view_audience: + type: string + download_audience: + type: string + record_publish_date: + type: string + notes: + type: string + title: + type: string + isInCatalog: + type: string + isDraft: + type: string + contacts: + items: + $ref: '#/components/schemas/DatasetContact' + type: array + resources: + items: + $ref: '#/components/schemas/DatasetResource' + type: array + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + organization: + $ref: '#/components/schemas/OrganizationRefID' + organizationUnit: + $ref: '#/components/schemas/OrganizationUnitRefID' + type: object + additionalProperties: false + BatchResult: + properties: + status: + type: number + format: double + result: + type: string + reason: + type: string + id: + type: string + ownedBy: + type: string + childResults: + items: + $ref: '#/components/schemas/BatchResult' + type: array + required: + - status + - result + type: object + additionalProperties: false + DraftDataset: + properties: + name: + type: string + license_title: + type: string + security_class: + type: string + enum: + - HIGH-CABINET + - HIGH-CONFIDENTIAL + - HIGH-SENSITIVITY + - MEDIUM-SENSITIVITY + - MEDIUM-PERSONAL + - LOW-SENSITIVITY + - LOW-PUBLIC + - PUBLIC + - 'PROTECTED A' + - 'PROTECTED B' + - 'PROTECTED C' + view_audience: + type: string + enum: + - Public + - Government + - 'Named users' + - 'Government and Business BCeID' + download_audience: + type: string + enum: + - Public + - Government + - 'Named users' + - 'Government and Business BCeID' + record_publish_date: + type: string + notes: + type: string + title: + type: string + isInCatalog: + type: boolean + isDraft: + type: boolean + contacts: + items: + $ref: '#/components/schemas/DatasetContact' + type: array + resources: + items: + $ref: '#/components/schemas/DatasetResource' + type: array + tags: + items: + type: string + type: array + organization: + $ref: '#/components/schemas/OrganizationRefID' + organizationUnit: + $ref: '#/components/schemas/OrganizationUnitRefID' + type: object + additionalProperties: false + example: + name: my_sample_dataset + license_title: 'Open Government Licence - British Columbia' + security_class: PUBLIC + view_audience: Public + download_audience: Public + record_publish_date: '2017-09-05' + notes: 'Some notes' + title: 'A title about my dataset' + tags: + - tag1 + - tag2 + organization: ministry-of-citizens-services + organizationUnit: databc + Gateway: + properties: + gatewayId: + type: string + displayName: + type: string + type: object + additionalProperties: false + ActivityDetail: + properties: + id: + type: string + message: + type: string + params: + properties: {} + additionalProperties: + type: string + type: object + activityAt: {} + blob: {} + required: + - message + - params + - activityAt + type: object + additionalProperties: false + PublishResult: + properties: + message: + type: string + results: + type: string + error: + type: string + type: object + additionalProperties: false + GatewayServiceRefID: + type: string + GatewayRouteRefID: + type: string + GatewayPlugin: + properties: + extForeignKey: + type: string + name: + type: string + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + config: {} + service: + $ref: '#/components/schemas/GatewayServiceRefID' + route: + $ref: '#/components/schemas/GatewayRouteRefID' + type: object + additionalProperties: false + GatewayRoute: + properties: + extForeignKey: + type: string + name: + type: string + gatewayId: + type: string + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + methods: + items: + type: string + type: array + paths: + items: + type: string + type: array + hosts: + items: + type: string + type: array + service: + $ref: '#/components/schemas/GatewayServiceRefID' + plugins: + items: + $ref: '#/components/schemas/GatewayPlugin' + type: array + type: object + additionalProperties: false + IssuerEnvironmentConfig: + properties: + environment: + type: string + exists: + type: boolean + issuerUrl: + type: string + clientRegistration: + type: string + enum: + - anonymous + - managed + - iat + clientId: + type: string + clientSecret: + type: string + initialAccessToken: + type: string + type: object + additionalProperties: false + example: + environment: dev + issuerUrl: 'https://idp.site/auth/realms/my-realm' + clientRegistration: managed + clientId: a-client-id + clientSecret: a-client-secret + undefinedRefID: + type: string + CredentialIssuer: + properties: + name: + type: string + gatewayId: + type: string + description: + type: string + flow: + type: string + enum: + - client-credentials + nullable: false + mode: + type: string + enum: + - auto + nullable: false + authPlugin: + type: string + clientAuthenticator: + type: string + enum: + - client-secret + - client-jwt + - client-jwt-jwks-url + instruction: + type: string + environmentDetails: + items: + $ref: '#/components/schemas/IssuerEnvironmentConfig' + type: array + resourceType: + type: string + resourceAccessScope: + type: string + isShared: + type: boolean + apiKeyName: + type: string + availableScopes: + items: + type: string + type: array + resourceScopes: + items: + type: string + type: array + clientRoles: + items: + type: string + type: array + clientMappers: + items: + type: string + type: array + inheritFrom: + $ref: '#/components/schemas/undefinedRefID' + owner: + $ref: '#/components/schemas/undefinedRefID' + type: object + additionalProperties: false + example: + name: my-auth-profile + description: 'Auth connection to my IdP' + flow: client-credentials + clientAuthenticator: client-secret + mode: auto + environmentDetails: [] + owner: janis@gov.bc.ca + OrganizationUnit: + properties: + extForeignKey: + type: string + name: + type: string + sector: + type: string + title: + type: string + description: + type: string + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + type: object + additionalProperties: false + Organization: + properties: + extForeignKey: + type: string + name: + type: string + sector: + type: string + title: + type: string + description: + type: string + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + orgUnits: + items: + $ref: '#/components/schemas/OrganizationUnit' + type: array + type: object + additionalProperties: false + GroupPermission: + properties: + resource: + type: string + scopes: + items: + type: string + type: array + required: + - scopes + type: object + additionalProperties: false + GroupRole: + properties: + name: + type: string + permissions: + items: + $ref: '#/components/schemas/GroupPermission' + type: array + required: + - name + - permissions + type: object + additionalProperties: false + GroupAccess: + properties: + name: + type: string + parent: + type: string + roles: + items: + $ref: '#/components/schemas/GroupRole' + type: array + required: + - roles + type: object + additionalProperties: false + UserReference: + properties: + id: + type: string + email: + type: string + type: object + additionalProperties: false + GroupMember: + properties: + member: + $ref: '#/components/schemas/UserReference' + roles: + items: + type: string + type: array + required: + - member + - roles + type: object + additionalProperties: false + GroupMembership: + properties: + name: + type: string + parent: + type: string + members: + items: + $ref: '#/components/schemas/GroupMember' + type: array + type: object + additionalProperties: false + OrgNamespace: + properties: + name: + type: string + orgUnit: + type: string + enabled: + type: boolean + updatedAt: + type: number + format: double + required: + - name + - orgUnit + - enabled + - updatedAt + type: object + additionalProperties: false + DraftDatasetRefID: + type: string + LegalRefID: + type: string + CredentialIssuerRefID: + type: string + Environment: + properties: + appId: + type: string + name: + type: string + enum: + - dev + - test + - prod + - sandbox + - other + active: + type: boolean + approval: + type: boolean + flow: + type: string + enum: + - public + - protected-externally + - authorization-code + - client-credentials + - kong-acl-only + - kong-api-key-only + - kong-api-key-acl + additionalDetailsToRequest: + type: string + services: + items: + $ref: '#/components/schemas/GatewayServiceRefID' + type: array + legal: + $ref: '#/components/schemas/LegalRefID' + credentialIssuer: + $ref: '#/components/schemas/CredentialIssuerRefID' + type: object + additionalProperties: false + example: + name: dev + active: false + approval: false + flow: public + appId: '00000000' + Product: + properties: + appId: + type: string + name: + type: string + description: + type: string + gatewayId: + type: string + dataset: + $ref: '#/components/schemas/DraftDatasetRefID' + environments: + items: + $ref: '#/components/schemas/Environment' + type: array + type: object + additionalProperties: false + example: + name: my-new-product + appId: '000000000000' + environments: + - + name: dev + active: false + approval: false + flow: public + appId: '00000000' + securitySchemes: + jwt: + type: oauth2 + description: 'Authz Client Credential' + flows: + clientCredentials: + tokenUrl: 'https://token_endpoint' + scopes: {} + portal: + type: http + description: 'Authz Portal Login' + scheme: bearer + bearerFormat: JWT + openid: + type: openIdConnect + description: 'OIDC Login' + openIdConnectUrl: 'https://well_known_endpoint' +info: + title: 'APS Directory API' + version: 3.0.0 + description: 'API Services Portal by BC Gov API Programme Services' + license: + name: MIT + contact: + name: 'BC Gov APS' +openapi: 3.0.0 +paths: + '/organizations/{org}/datasets': + get: + operationId: organization-datasets + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/Dataset' + type: array + description: "Get metadata about Datasets that are available by API for this organization\n> `Required Scope:` Dataset.Manage" + summary: 'Get Organization Datasets' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + put: + operationId: put-organization-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Manage metadata about Datasets that are available by API for this organization\n> `Required Scope:` Dataset.Manage" + summary: 'Manage Organization Datasets' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DraftDataset' + '/organizations/{org}/datasets/{name}': + delete: + operationId: delete-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Delete a Dataset\n> `Required Scope:` Dataset.Manage" + summary: 'Delete a dataset' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: path + name: name + required: true + schema: + type: string + get: + operationId: get-organization-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Dataset' + description: "Get metadata about a Dataset that are available by API for this organization\n> `Required Scope:` Dataset.Manage" + summary: 'Get Organization Dataset' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: path + name: name + required: true + schema: + type: string + /directory: + get: + operationId: directory-list + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - 'API Directory' + security: [] + parameters: [] + '/directory/{id}': + get: + operationId: directory-item + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - 'API Directory' + security: [] + parameters: + - + in: path + name: id + required: true + schema: + type: string + '/gateways/{gatewayId}/datasets': + put: + operationId: put-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Update metadata about a Dataset\n> `Required Scope:` Namespace.Manage" + summary: 'Update Dataset' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DraftDataset' + '/gateways/{gatewayId}/datasets/{name}': + get: + operationId: get-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Dataset' + description: "Get metadata about a Dataset\n> `Required Scope:` Namespace.Manage" + summary: 'Get Dataset' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: name + required: true + schema: + type: string + /routes/availability: + get: + operationId: check-availability + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - 'Service Routes' + security: [] + parameters: + - + in: query + name: serviceName + required: true + schema: + type: string + - + in: query + name: gatewayId + required: true + schema: + type: string + /gateways/report: + get: + operationId: report + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - Gateways + security: + - + jwt: [] + parameters: + - + in: query + name: ids + required: false + schema: + default: '[]' + type: string + /gateways: + get: + operationId: gateway-list + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/Gateway' + type: array + summary: 'List of Gateways available to the user' + tags: + - Gateways + security: + - + jwt: [] + parameters: [] + post: + operationId: create-gateway + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Gateway' + description: 'Create a gateway' + summary: 'Create Gateway' + tags: + - Gateways + security: + - + jwt: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Gateway' + '/gateways/{gatewayId}': + get: + operationId: namespace-profile + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Gateway' + description: "Get details about the gateway, such as permissions for what the gateway is setup with.\n> `Required Scope:` Namespace.Manage" + summary: 'Gateway Summary' + tags: + - Gateways + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + delete: + operationId: delete-namespace + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Gateway' + description: "Delete the gateway\n> `Required Scope:` Namespace.Manage" + summary: 'Delete Gateway' + tags: + - Gateways + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: query + name: force + required: false + schema: + default: false + type: boolean + '/gateways/{gatewayId}/activity': + get: + operationId: gateway-admin-activity + responses: + '200': + description: 'Activity[]' + content: + application/json: + schema: + items: + $ref: '#/components/schemas/ActivityDetail' + type: array + description: '> `Required Scope:` Namespace.View' + summary: 'Get administration activity for this gateway' + tags: + - Gateways + security: + - + jwt: + - Namespace.View + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: query + name: first + required: false + schema: + default: 20 + format: double + type: number + - + in: query + name: skip + required: false + schema: + default: 0 + format: double + type: number + '/gateways/{gatewayId}/links': + get: + operationId: get-gateway-links + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + properties: {host: {type: string}} + required: [host] + type: object + type: array + description: "Get a summary of your endpoints\n> `Required Scope:` Namespace.Manage" + summary: 'Get endpoints' + tags: + - Gateways + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + '/gateways/{gatewayId}/directory/{id}': + get: + operationId: get-ns-directory-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: {} + description: "Used primarily for \"Preview Mode\"\nGet a particular Dataset" + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: id + required: true + schema: + type: string + '/gateways/{gatewayId}/directory': + get: + operationId: get-ns-directory + responses: + '200': + description: Ok + content: + application/json: + schema: {} + description: "Used primarily for \"Preview Mode\"\nList the datasets belonging to a particular namespace" + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + '/gateways/{gatewayId}/services': + put: + operationId: publish-gateway-config + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResult' + tags: + - 'Gateway Services' + security: + - + jwt: + - Gateway.Config + parameters: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + dryRun: + type: string + configFile: + type: string + format: binary + required: + - dryRun + - configFile + get: + operationId: get-gateway-routes + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/GatewayRoute' + type: array + description: "Get a summary of your Gateway Services\n> `Required Scope:` Namespace.Manage" + summary: 'Get Gateway Services' + tags: + - 'Gateway Services' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + '/identifiers/{type}': + get: + operationId: GetNewID + responses: + '200': + description: Ok + content: + application/json: + schema: + type: string + tags: + - 'New Identifiers' + security: [] + parameters: + - + in: path + name: type + required: true + schema: + type: string + enum: + - environment + - product + - application + - gateway + '/gateways/{gatewayId}/issuers': + put: + operationId: put-issuer + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Create or Update Authorization Profiles\n> `Required Scope:` CredentialIssuer.Admin" + summary: 'Manage Authorization Profiles' + tags: + - 'Authorization Profiles' + security: + - + jwt: + - CredentialIssuer.Admin + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CredentialIssuer' + get: + operationId: get-issuers + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/CredentialIssuer' + type: array + description: "Get Authorization Profiles setup in this namespace\n> `Required Scope:` Namespace.Manage" + summary: 'Get Authorization Profiles' + tags: + - 'Authorization Profiles' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + '/gateways/{gatewayId}/issuers/{name}': + delete: + operationId: delete-issuer + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Delete an Authorization Profile\n> `Required Scope:` CredentialIssuer.Admin" + summary: 'Delete Profile' + tags: + - 'Authorization Profiles' + security: + - + jwt: + - CredentialIssuer.Admin + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: name + required: true + schema: + type: string + /organizations: + get: + operationId: organization-list + responses: + '200': + description: Ok + content: + application/json: + schema: + items: {} + type: array + tags: + - Organizations + security: [] + parameters: [] + '/organizations/{org}': + put: + operationId: put-organization + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Create Organization\n> `Required Scope:` GroupAccess.Manage" + summary: 'Create Organizations' + tags: + - Organizations + security: + - + jwt: + - GroupAccess.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Organization' + get: + operationId: organization-units + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - Organizations + security: [] + parameters: + - + in: path + name: org + required: true + schema: + type: string + '/organizations/{org}/roles': + get: + operationId: get-organization-roles + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GroupAccess' + description: '> `Required Scope:` GroupAccess.Manage' + tags: + - Organizations + security: + - + jwt: + - GroupAccess.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + '/organizations/{org}/access': + get: + operationId: get-organization-access + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GroupMembership' + description: '> `Required Scope:` GroupAccess.Manage' + tags: + - Organizations + security: + - + jwt: + - GroupAccess.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + put: + operationId: put-organization-access + responses: + '204': + description: 'No content' + description: '> `Required Scope:` GroupAccess.Manage' + tags: + - Organizations + security: + - + jwt: + - GroupAccess.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupMembership' + '/organizations/{org}/gateways': + get: + operationId: organization-gateways + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/OrgNamespace' + type: array + description: '> `Required Scope:` Namespace.Assign' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + '/organizations/{org}/{orgUnit}/gateways/{gatewayId}': + put: + operationId: assign-namespace-to-organization + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + result: {type: string} + required: + - result + type: object + description: '> `Required Scope:` Namespace.Assign' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: path + name: orgUnit + required: true + schema: + type: string + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: query + name: enable + required: false + schema: + default: true + type: boolean + delete: + operationId: unassign-namespace-from-organization + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + result: {type: string} + required: + - result + type: object + description: '> `Required Scope:` Namespace.Assign' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: path + name: orgUnit + required: true + schema: + type: string + - + in: path + name: gatewayId + required: true + schema: + type: string + '/organizations/{org}/activity': + get: + operationId: org-namespace-activity + responses: + '200': + description: 'Activity[]' + content: + application/json: + schema: + items: + $ref: '#/components/schemas/ActivityDetail' + type: array + description: '> `Required Scope:` Namespace.Assign' + summary: 'Get administration activity for gateways associated with this Organization' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: query + name: first + required: false + schema: + default: 20 + format: double + type: number + - + in: query + name: skip + required: false + schema: + default: 0 + format: double + type: number + /roles: + get: + operationId: GetRoles + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - Organizations + security: [] + parameters: [] + '/gateways/{gatewayId}/products': + put: + operationId: put-product + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Manage Products for APIs that will appear on the API Directory\n> `Required Scope:` Namespace.Manage" + summary: 'Manage Products' + tags: + - Products + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + get: + operationId: get-products + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/Product' + type: array + description: "Get Products describing APIs that will appear on the API Directory\n> `Required Scope:` Namespace.Manage" + summary: 'Get Products' + tags: + - Products + security: + - + jwt: + - Namespace.Manage + parameters: [] + '/gateways/{gatewayId}/products/{appId}': + delete: + operationId: delete-product + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Delete a Product\n> `Required Scope:` Namespace.Manage" + summary: 'Manage Products' + tags: + - Products + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: appId + required: true + schema: + type: string + '/gateways/{gatewayId}/environments/{appId}': + delete: + operationId: delete-product-environment + responses: + '204': + description: 'No content' + description: "Delete a Product Environment\n> `Required Scope:` Namespace.Manage" + summary: 'Delete a Product Environment' + tags: + - Products + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: appId + required: true + schema: + type: string + - + in: query + name: force + required: false + schema: + default: false + type: boolean +servers: + - + url: /ds/api/v3 +tags: + - + name: 'API Directory' + description: 'Discover all the great BC Government APIs' + - + name: 'API Directory (Administration)' + description: 'Administer datasets on the API Directory' + - + name: Organizations + description: 'Manage organizational access control' + - + name: Gateways + description: 'Get aggregated information about gateways' + - + name: 'Gateway Services' + description: 'View your Gateway Service details' + - + name: Products + description: 'Manage your Products and Environments for publishing to the API Directory' + - + name: 'Authorization Profiles' + description: 'Configure the integration to external Identity Providers' diff --git a/src/controllers/v3/routes.ts b/src/controllers/v3/routes.ts new file mode 100644 index 000000000..3de19b1d6 --- /dev/null +++ b/src/controllers/v3/routes.ts @@ -0,0 +1,1726 @@ +/* tslint:disable */ +/* eslint-disable */ +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { Controller, ValidationService, FieldErrors, ValidateError, TsoaRoute, HttpStatusCodeLiteral, TsoaResponse } from '@tsoa/runtime'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { OrgDatasetController } from './OrgDatasetController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { DirectoryController } from './DirectoryController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { DatasetController } from './DatasetController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { EndpointsController } from './EndpointsController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { NamespaceController } from './GatewayController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { GatewayDirectoryController } from './GatewayDirectoryController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { GatewayController } from './GatewayServicesController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { IdentifiersController } from './IdentifierController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { IssuerController } from './IssuerController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { OrganizationController } from './OrganizationController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { OrgRoleController } from './OrgRoleController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { ProductController } from './ProductController'; +import { expressAuthentication } from './../../auth/auth-tsoa'; +// @ts-ignore - no great way to install types from subpackage +const promiseAny = require('promise.any'); +import { iocContainer } from './../ioc'; +import { IocContainer, IocContainerFactory } from '@tsoa/runtime'; +import * as express from 'express'; +const multer = require('multer'); +const upload = multer(); + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + +const models: TsoaRoute.Models = { + "DatasetContact": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "email": {"dataType":"string"}, + "role": {"dataType":"enum","enums":["pointOfContact"]}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DatasetResource": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string"}, + "name": {"dataType":"string"}, + "format": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["openapi-json"]},{"dataType":"enum","enums":["json"]}]}, + "url": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrganizationRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrganizationUnitRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Dataset": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "license_title": {"dataType":"string"}, + "security_class": {"dataType":"string"}, + "view_audience": {"dataType":"string"}, + "download_audience": {"dataType":"string"}, + "record_publish_date": {"dataType":"string"}, + "notes": {"dataType":"string"}, + "title": {"dataType":"string"}, + "isInCatalog": {"dataType":"string"}, + "isDraft": {"dataType":"string"}, + "contacts": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetContact"}}, + "resources": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetResource"}}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "organization": {"ref":"OrganizationRefID"}, + "organizationUnit": {"ref":"OrganizationUnitRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BatchResult": { + "dataType": "refObject", + "properties": { + "status": {"dataType":"double","required":true}, + "result": {"dataType":"string","required":true}, + "reason": {"dataType":"string"}, + "id": {"dataType":"string"}, + "ownedBy": {"dataType":"string"}, + "childResults": {"dataType":"array","array":{"dataType":"refObject","ref":"BatchResult"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DraftDataset": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "license_title": {"dataType":"string"}, + "security_class": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["HIGH-CABINET"]},{"dataType":"enum","enums":["HIGH-CONFIDENTIAL"]},{"dataType":"enum","enums":["HIGH-SENSITIVITY"]},{"dataType":"enum","enums":["MEDIUM-SENSITIVITY"]},{"dataType":"enum","enums":["MEDIUM-PERSONAL"]},{"dataType":"enum","enums":["LOW-SENSITIVITY"]},{"dataType":"enum","enums":["LOW-PUBLIC"]},{"dataType":"enum","enums":["PUBLIC"]},{"dataType":"enum","enums":["PROTECTED A"]},{"dataType":"enum","enums":["PROTECTED B"]},{"dataType":"enum","enums":["PROTECTED C"]}]}, + "view_audience": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["Public"]},{"dataType":"enum","enums":["Government"]},{"dataType":"enum","enums":["Named users"]},{"dataType":"enum","enums":["Government and Business BCeID"]}]}, + "download_audience": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["Public"]},{"dataType":"enum","enums":["Government"]},{"dataType":"enum","enums":["Named users"]},{"dataType":"enum","enums":["Government and Business BCeID"]}]}, + "record_publish_date": {"dataType":"string"}, + "notes": {"dataType":"string"}, + "title": {"dataType":"string"}, + "isInCatalog": {"dataType":"boolean"}, + "isDraft": {"dataType":"boolean"}, + "contacts": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetContact"}}, + "resources": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetResource"}}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "organization": {"ref":"OrganizationRefID"}, + "organizationUnit": {"ref":"OrganizationUnitRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Gateway": { + "dataType": "refObject", + "properties": { + "gatewayId": {"dataType":"string"}, + "displayName": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ActivityDetail": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string"}, + "message": {"dataType":"string","required":true}, + "params": {"dataType":"nestedObjectLiteral","nestedProperties":{},"additionalProperties":{"dataType":"string"},"required":true}, + "activityAt": {"dataType":"any","required":true}, + "blob": {"dataType":"any"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "PublishResult": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string"}, + "results": {"dataType":"string"}, + "error": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayServiceRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayRouteRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayPlugin": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "config": {"dataType":"any"}, + "service": {"ref":"GatewayServiceRefID"}, + "route": {"ref":"GatewayRouteRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayRoute": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "gatewayId": {"dataType":"string"}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "methods": {"dataType":"array","array":{"dataType":"string"}}, + "paths": {"dataType":"array","array":{"dataType":"string"}}, + "hosts": {"dataType":"array","array":{"dataType":"string"}}, + "service": {"ref":"GatewayServiceRefID"}, + "plugins": {"dataType":"array","array":{"dataType":"refObject","ref":"GatewayPlugin"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IssuerEnvironmentConfig": { + "dataType": "refObject", + "properties": { + "environment": {"dataType":"string"}, + "exists": {"dataType":"boolean"}, + "issuerUrl": {"dataType":"string"}, + "clientRegistration": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["anonymous"]},{"dataType":"enum","enums":["managed"]},{"dataType":"enum","enums":["iat"]}]}, + "clientId": {"dataType":"string"}, + "clientSecret": {"dataType":"string"}, + "initialAccessToken": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "undefinedRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CredentialIssuer": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "gatewayId": {"dataType":"string"}, + "description": {"dataType":"string"}, + "flow": {"dataType":"enum","enums":["client-credentials"]}, + "mode": {"dataType":"enum","enums":["auto"]}, + "authPlugin": {"dataType":"string"}, + "clientAuthenticator": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["client-secret"]},{"dataType":"enum","enums":["client-jwt"]},{"dataType":"enum","enums":["client-jwt-jwks-url"]}]}, + "instruction": {"dataType":"string"}, + "environmentDetails": {"dataType":"array","array":{"dataType":"refObject","ref":"IssuerEnvironmentConfig"}}, + "resourceType": {"dataType":"string"}, + "resourceAccessScope": {"dataType":"string"}, + "isShared": {"dataType":"boolean"}, + "apiKeyName": {"dataType":"string"}, + "availableScopes": {"dataType":"array","array":{"dataType":"string"}}, + "resourceScopes": {"dataType":"array","array":{"dataType":"string"}}, + "clientRoles": {"dataType":"array","array":{"dataType":"string"}}, + "clientMappers": {"dataType":"array","array":{"dataType":"string"}}, + "inheritFrom": {"ref":"undefinedRefID"}, + "owner": {"ref":"undefinedRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrganizationUnit": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "sector": {"dataType":"string"}, + "title": {"dataType":"string"}, + "description": {"dataType":"string"}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Organization": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "sector": {"dataType":"string"}, + "title": {"dataType":"string"}, + "description": {"dataType":"string"}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "orgUnits": {"dataType":"array","array":{"dataType":"refObject","ref":"OrganizationUnit"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupPermission": { + "dataType": "refObject", + "properties": { + "resource": {"dataType":"string"}, + "scopes": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupRole": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string","required":true}, + "permissions": {"dataType":"array","array":{"dataType":"refObject","ref":"GroupPermission"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupAccess": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "parent": {"dataType":"string"}, + "roles": {"dataType":"array","array":{"dataType":"refObject","ref":"GroupRole"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UserReference": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string"}, + "email": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupMember": { + "dataType": "refObject", + "properties": { + "member": {"ref":"UserReference","required":true}, + "roles": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupMembership": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "parent": {"dataType":"string"}, + "members": {"dataType":"array","array":{"dataType":"refObject","ref":"GroupMember"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrgNamespace": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string","required":true}, + "orgUnit": {"dataType":"string","required":true}, + "enabled": {"dataType":"boolean","required":true}, + "updatedAt": {"dataType":"double","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DraftDatasetRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "LegalRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CredentialIssuerRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Environment": { + "dataType": "refObject", + "properties": { + "appId": {"dataType":"string"}, + "name": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["dev"]},{"dataType":"enum","enums":["test"]},{"dataType":"enum","enums":["prod"]},{"dataType":"enum","enums":["sandbox"]},{"dataType":"enum","enums":["other"]}]}, + "active": {"dataType":"boolean"}, + "approval": {"dataType":"boolean"}, + "flow": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["public"]},{"dataType":"enum","enums":["protected-externally"]},{"dataType":"enum","enums":["authorization-code"]},{"dataType":"enum","enums":["client-credentials"]},{"dataType":"enum","enums":["kong-acl-only"]},{"dataType":"enum","enums":["kong-api-key-only"]},{"dataType":"enum","enums":["kong-api-key-acl"]}]}, + "additionalDetailsToRequest": {"dataType":"string"}, + "services": {"dataType":"array","array":{"dataType":"refAlias","ref":"GatewayServiceRefID"}}, + "legal": {"ref":"LegalRefID"}, + "credentialIssuer": {"ref":"CredentialIssuerRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Product": { + "dataType": "refObject", + "properties": { + "appId": {"dataType":"string"}, + "name": {"dataType":"string"}, + "description": {"dataType":"string"}, + "gatewayId": {"dataType":"string"}, + "dataset": {"ref":"DraftDatasetRefID"}, + "environments": {"dataType":"array","array":{"dataType":"refObject","ref":"Environment"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +}; +const validationService = new ValidationService(models); + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + +export function RegisterRoutes(app: express.Router) { + // ########################################################################################################### + // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look + // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa + // ########################################################################################################### + app.get('/ds/api/v3/organizations/:org/datasets', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgDatasetController_getDatasets(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgDatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDatasets.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org/datasets', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgDatasetController_putDataset(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"DraftDataset"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgDatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.putDataset.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/organizations/:org/datasets/:name', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgDatasetController_delete(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + name: {"in":"path","name":"name","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgDatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.delete.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/datasets/:name', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgDatasetController_getDataset(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + name: {"in":"path","name":"name","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgDatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDataset.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/directory', + + async function DirectoryController_list(request: any, response: any, next: any) { + const args = { + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(DirectoryController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.list.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/directory/:id', + + async function DirectoryController_get(request: any, response: any, next: any) { + const args = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(DirectoryController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/gateways/:gatewayId/datasets', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function DatasetController_put(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"DraftDataset"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(DatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/datasets/:name', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function DatasetController_getDataset(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + name: {"in":"path","name":"name","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(DatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDataset.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/routes/availability', + + async function EndpointsController_check(request: any, response: any, next: any) { + const args = { + serviceName: {"in":"query","name":"serviceName","required":true,"dataType":"string"}, + gatewayId: {"in":"query","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(EndpointsController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.check.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/report', + authenticateMiddleware([{"jwt":[]}]), + + async function NamespaceController_report(request: any, response: any, next: any) { + const args = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + ids: {"default":"[]","in":"query","name":"ids","dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.report.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways', + authenticateMiddleware([{"jwt":[]}]), + + async function NamespaceController_list(request: any, response: any, next: any) { + const args = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.list.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function NamespaceController_profile(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.profile.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/ds/api/v3/gateways', + authenticateMiddleware([{"jwt":[]}]), + + async function NamespaceController_create(request: any, response: any, next: any) { + const args = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + vars: {"in":"body","name":"vars","required":true,"ref":"Gateway"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.create.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/gateways/:gatewayId', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function NamespaceController_delete(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + force: {"default":false,"in":"query","name":"force","dataType":"boolean"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.delete.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/activity', + authenticateMiddleware([{"jwt":["Namespace.View"]}]), + + async function NamespaceController_namespaceActivity(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + first: {"default":20,"in":"query","name":"first","dataType":"double"}, + skip: {"default":0,"in":"query","name":"skip","dataType":"double"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.namespaceActivity.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/links', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function NamespaceController_get(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/directory/:id', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function GatewayDirectoryController_getDataset(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(GatewayDirectoryController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDataset.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/directory', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function GatewayDirectoryController_getDatasets(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(GatewayDirectoryController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDatasets.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/gateways/:gatewayId/services', + authenticateMiddleware([{"jwt":["Gateway.Config"]}]), + upload.single('configFile'), + + async function GatewayController_put(request: any, response: any, next: any) { + const args = { + dryRun: {"in":"formData","name":"dryRun","required":true,"dataType":"string"}, + configFile: {"in":"formData","name":"configFile","required":true,"dataType":"file"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(GatewayController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/services', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function GatewayController_getServices(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(GatewayController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getServices.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/identifiers/:type', + + async function IdentifiersController_getNewID(request: any, response: any, next: any) { + const args = { + type: {"in":"path","name":"type","required":true,"dataType":"union","subSchemas":[{"dataType":"enum","enums":["environment"]},{"dataType":"enum","enums":["product"]},{"dataType":"enum","enums":["application"]},{"dataType":"enum","enums":["gateway"]}]}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(IdentifiersController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getNewID.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/gateways/:gatewayId/issuers', + authenticateMiddleware([{"jwt":["CredentialIssuer.Admin"]}]), + + async function IssuerController_put(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"CredentialIssuer"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(IssuerController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/issuers', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function IssuerController_get(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(IssuerController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/gateways/:gatewayId/issuers/:name', + authenticateMiddleware([{"jwt":["CredentialIssuer.Admin"]}]), + + async function IssuerController_delete(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + name: {"in":"path","name":"name","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(IssuerController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.delete.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations', + + async function OrganizationController_listOrganizations(request: any, response: any, next: any) { + const args = { + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.listOrganizations.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org', + authenticateMiddleware([{"jwt":["GroupAccess.Manage"]}]), + + async function OrganizationController_post(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"Organization"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.post.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org', + + async function OrganizationController_listOrganizationUnits(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.listOrganizationUnits.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/roles', + authenticateMiddleware([{"jwt":["GroupAccess.Manage"]}]), + + async function OrganizationController_getPolicies(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getPolicies.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/access', + authenticateMiddleware([{"jwt":["GroupAccess.Manage"]}]), + + async function OrganizationController_get(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org/access', + authenticateMiddleware([{"jwt":["GroupAccess.Manage"]}]), + + async function OrganizationController_put(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"GroupMembership"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/gateways', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrganizationController_listNamespaces(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.listNamespaces.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org/:orgUnit/gateways/:gatewayId', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrganizationController_assignNamespace(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + orgUnit: {"in":"path","name":"orgUnit","required":true,"dataType":"string"}, + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + enable: {"default":true,"in":"query","name":"enable","dataType":"boolean"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.assignNamespace.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/organizations/:org/:orgUnit/gateways/:gatewayId', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrganizationController_unassignNamespace(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + orgUnit: {"in":"path","name":"orgUnit","required":true,"dataType":"string"}, + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.unassignNamespace.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/activity', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrganizationController_namespaceActivity(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + first: {"default":20,"in":"query","name":"first","dataType":"double"}, + skip: {"default":0,"in":"query","name":"skip","dataType":"double"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.namespaceActivity.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/roles', + + async function OrgRoleController_getRoles(request: any, response: any, next: any) { + const args = { + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgRoleController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getRoles.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/gateways/:gatewayId/products', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function ProductController_put(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"Product"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(ProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/products', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function ProductController_get(request: any, response: any, next: any) { + const args = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(ProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/gateways/:gatewayId/products/:appId', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function ProductController_delete(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + appId: {"in":"path","name":"appId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(ProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.delete.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/gateways/:gatewayId/environments/:appId', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function ProductController_deleteEnvironment(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + appId: {"in":"path","name":"appId","required":true,"dataType":"string"}, + force: {"default":false,"in":"query","name":"force","dataType":"boolean"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(ProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.deleteEnvironment.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function authenticateMiddleware(security: TsoaRoute.Security[] = []) { + return async function runAuthenticationMiddleware(request: any, _response: any, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // keep track of failed auth attempts so we can hand back the most + // recent one. This behavior was previously existing so preserving it + // here + const failedAttempts: any[] = []; + const pushAndRethrow = (error: any) => { + failedAttempts.push(error); + throw error; + }; + + const secMethodOrPromises: Promise[] = []; + for (const secMethod of security) { + if (Object.keys(secMethod).length > 1) { + const secMethodAndPromises: Promise[] = []; + + for (const name in secMethod) { + secMethodAndPromises.push( + expressAuthentication(request, name, secMethod[name]) + .catch(pushAndRethrow) + ); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + secMethodOrPromises.push(Promise.all(secMethodAndPromises) + .then(users => { return users[0]; })); + } else { + for (const name in secMethod) { + secMethodOrPromises.push( + expressAuthentication(request, name, secMethod[name]) + .catch(pushAndRethrow) + ); + } + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + try { + request['user'] = await promiseAny(secMethodOrPromises); + next(); + } + catch(err) { + // Show most recent error as response + const error = failedAttempts.pop(); + error.status = error.status || 401; + next(error); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function isController(object: any): object is Controller { + return 'getHeaders' in object && 'getStatus' in object && 'setStatus' in object; + } + + function promiseHandler(controllerObj: any, promise: any, response: any, successStatus: any, next: any) { + return Promise.resolve(promise) + .then((data: any) => { + let statusCode = successStatus; + let headers; + if (isController(controllerObj)) { + headers = controllerObj.getHeaders(); + statusCode = controllerObj.getStatus() || statusCode; + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + returnHandler(response, statusCode, data, headers) + }) + .catch((error: any) => next(error)); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function returnHandler(response: any, statusCode?: number, data?: any, headers: any = {}) { + if (response.headersSent) { + return; + } + Object.keys(headers).forEach((name: string) => { + response.set(name, headers[name]); + }); + if (data && typeof data.pipe === 'function' && data.readable && typeof data._read === 'function') { + data.pipe(response); + } else if (data !== null && data !== undefined) { + response.status(statusCode || 200).json(data); + } else { + response.status(statusCode || 204).end(); + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function responder(response: any): TsoaResponse { + return function(status, data, headers) { + returnHandler(response, status, data, headers); + }; + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function getValidatedArgs(args: any, request: any, response: any): any[] { + const fieldErrors: FieldErrors = {}; + const values = Object.keys(args).map((key) => { + const name = args[key].name; + switch (args[key].in) { + case 'request': + return request; + case 'query': + return validationService.ValidateParam(args[key], request.query[name], name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'path': + return validationService.ValidateParam(args[key], request.params[name], name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'header': + return validationService.ValidateParam(args[key], request.header(name), name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'body': + return validationService.ValidateParam(args[key], request.body, name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'body-prop': + return validationService.ValidateParam(args[key], request.body[name], name, fieldErrors, 'body.', {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'formData': + if (args[key].dataType === 'file') { + return validationService.ValidateParam(args[key], request.file, name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + } else if (args[key].dataType === 'array' && args[key].array.dataType === 'file') { + return validationService.ValidateParam(args[key], request.files, name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + } else { + return validationService.ValidateParam(args[key], request.body[name], name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + } + case 'res': + return responder(response); + } + }); + + if (Object.keys(fieldErrors).length > 0) { + throw new ValidateError(fieldErrors, ''); + } + return values; + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +} + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/src/controllers/v3/types-extra.ts b/src/controllers/v3/types-extra.ts new file mode 100644 index 000000000..438ddde0d --- /dev/null +++ b/src/controllers/v3/types-extra.ts @@ -0,0 +1,21 @@ +import { Scalars } from '../../services/keystone/types'; + +/** + * @tsoaModel + */ +export interface ActivityDetail { + id?: string; + message: string; + params: { [key: string]: string }; + activityAt: Scalars['DateTime']; + blob?: any; +} + +/** + * @tsoaModel + */ +export interface PublishResult { + message?: string; + results?: string; + error?: string; +} diff --git a/src/controllers/v3/types.ts b/src/controllers/v3/types.ts new file mode 100644 index 000000000..7157354c0 --- /dev/null +++ b/src/controllers/v3/types.ts @@ -0,0 +1,597 @@ + +/********************************************/ +/***** WARNING!!!! THIS IS AUTO-GENERATED ***/ +/***** RUNING: npm run tsoa-gen-types ***/ +/********************************************/ + +export type DateTime = any; + + +/** + * @tsoaModel + * + */ +export interface Organization { + extForeignKey?: string; // Primary Key + name?: string; + sector?: string; + title?: string; + description?: string; + extSource?: string; + extRecordHash?: string; + tags?: string[]; + orgUnits?: OrganizationUnit[]; +} + + +/** + * @tsoaModel + * + */ +export interface OrganizationUnit { + extForeignKey?: string; // Primary Key + name?: string; + sector?: string; + title?: string; + description?: string; + extSource?: string; + extRecordHash?: string; + tags?: string[]; +} + + +/** + * @tsoaModel + * + */ +export interface Dataset { + extForeignKey?: string; // Primary Key + name?: string; + license_title?: string; + security_class?: string; + view_audience?: string; + download_audience?: string; + record_publish_date?: string; + notes?: string; + title?: string; + isInCatalog?: string; + isDraft?: string; + contacts?: DatasetContact[]; + resources?: DatasetResource[]; + extSource?: string; + extRecordHash?: string; + tags?: string[]; + organization?: OrganizationRefID; + organizationUnit?: OrganizationUnitRefID; +} + + +/** + * @tsoaModel + * @example { + * "name": "my_sample_dataset", + * "license_title": "Open Government Licence - British Columbia", + * "security_class": "PUBLIC", + * "view_audience": "Public", + * "download_audience": "Public", + * "record_publish_date": "2017-09-05", + * "notes": "Some notes", + * "title": "A title about my dataset", + * "tags": [ + * "tag1", + * "tag2" + * ], + * "organization": "ministry-of-citizens-services", + * "organizationUnit": "databc" + * } + */ +export interface DraftDataset { + name?: string; // Primary Key + license_title?: string; + security_class?: "HIGH-CABINET" | "HIGH-CONFIDENTIAL" | "HIGH-SENSITIVITY" | "MEDIUM-SENSITIVITY" | "MEDIUM-PERSONAL" | "LOW-SENSITIVITY" | "LOW-PUBLIC" | "PUBLIC" | "PROTECTED A" | "PROTECTED B" | "PROTECTED C"; + view_audience?: "Public" | "Government" | "Named users" | "Government and Business BCeID"; + download_audience?: "Public" | "Government" | "Named users" | "Government and Business BCeID"; + record_publish_date?: string; + notes?: string; + title?: string; + isInCatalog?: boolean; + isDraft?: boolean; + contacts?: DatasetContact[]; + resources?: DatasetResource[]; + tags?: string[]; + organization?: OrganizationRefID; + organizationUnit?: OrganizationUnitRefID; +} + + +/** + * @tsoaModel + * + */ +export interface Metric { + name?: string; // Primary Key + query?: string; + day?: string; + metric?: any; // toString + values?: any; // toString + service?: GatewayServiceRefID; +} + + +/** + * @tsoaModel + * + */ +export interface Alert { + name?: string; // Primary Key +} + + +/** + * @tsoaModel + * + */ +export interface Namespace { + extRefId?: string; // Primary Key + name?: string; + displayName?: string; +} + + +/** + * @tsoaModel + * + */ +export interface Gateway { + gatewayId?: string; // Primary Key + displayName?: string; +} + + +/** + * @tsoaModel + * + */ +export interface MemberRole { + extRefId?: string; // Primary Key + role?: string; + user?: UserRefID; +} + + +/** + * @tsoaModel + * + */ +export interface GatewayService { + extForeignKey?: string; // Primary Key + name?: string; + gatewayId?: string; + host?: string; + extSource?: string; + extRecordHash?: string; + tags?: string[]; + plugins?: GatewayPlugin[]; +} + + +/** + * @tsoaModel + * + */ +export interface GatewayGroup { + extForeignKey?: string; // Primary Key + name?: string; + gatewayId?: string; + extSource?: string; + extRecordHash?: string; +} + + +/** + * @tsoaModel + * + */ +export interface GatewayRoute { + extForeignKey?: string; // Primary Key + name?: string; + gatewayId?: string; + extSource?: string; + extRecordHash?: string; + tags?: string[]; + methods?: string[]; + paths?: string[]; + hosts?: string[]; + service?: GatewayServiceRefID; + plugins?: GatewayPlugin[]; +} + + +/** + * @tsoaModel + * + */ +export interface GatewayPlugin { + extForeignKey?: string; // Primary Key + name?: string; + extSource?: string; + extRecordHash?: string; + tags?: string[]; + config?: any; // toString + service?: GatewayServiceRefID; + route?: GatewayRouteRefID; +} + + +/** + * @tsoaModel + * + */ +export interface GatewayConsumer { + extForeignKey?: string; // Primary Key + username?: string; + customId?: string; + gatewayId?: string; + extSource?: string; + extRecordHash?: string; + tags?: string[]; + aclGroups?: string[]; + plugins?: GatewayPlugin[]; +} + + +/** + * @tsoaModel + * + */ +export interface ServiceAccess { + name?: string; // Primary Key + active?: string; + aclEnabled?: string; + consumerType?: string; + application?: ApplicationRefID; + consumer?: GatewayConsumerRefID; + productEnvironment?: EnvironmentRefID; +} + + +/** + * @tsoaModel + * + */ +export interface Application { + appId?: string; // Primary Key + name?: string; + description?: string; + owner?: UserRefID; + organization?: OrganizationRefID; + organizationUnit?: OrganizationUnitRefID; +} + + +/** + * @tsoaModel + * @example { + * "name": "my-new-product", + * "appId": "000000000000", + * "environments": [ + * { + * "name": "dev", + * "active": false, + * "approval": false, + * "flow": "public", + * "appId": "00000000" + * } + * ] + * } + */ +export interface Product { + appId?: string; // Primary Key + name?: string; + description?: string; + gatewayId?: string; + dataset?: DraftDatasetRefID; + environments?: Environment[]; +} + + +/** + * @tsoaModel + * @example { + * "name": "dev", + * "active": false, + * "approval": false, + * "flow": "public", + * "appId": "00000000" + * } + */ +export interface Environment { + appId?: string; // Primary Key + name?: "dev" | "test" | "prod" | "sandbox" | "other"; + active?: boolean; + approval?: boolean; + flow?: "public" | "protected-externally" | "authorization-code" | "client-credentials" | "kong-acl-only" | "kong-api-key-only" | "kong-api-key-acl"; + additionalDetailsToRequest?: string; + services?: GatewayServiceRefID[]; + legal?: LegalRefID; + credentialIssuer?: CredentialIssuerRefID; +} + + +/** + * @tsoaModel + * @example { + * "name": "my-auth-profile", + * "description": "Auth connection to my IdP", + * "flow": "client-credentials", + * "clientAuthenticator": "client-secret", + * "mode": "auto", + * "environmentDetails": [], + * "owner": "janis@gov.bc.ca" + * } + */ +export interface CredentialIssuer { + name?: string; // Primary Key + gatewayId?: string; + description?: string; + flow?: "client-credentials"; + mode?: "auto"; + authPlugin?: string; + clientAuthenticator?: "client-secret" | "client-jwt" | "client-jwt-jwks-url"; + instruction?: string; + environmentDetails?: IssuerEnvironmentConfig[]; + resourceType?: string; + resourceAccessScope?: string; + isShared?: boolean; + apiKeyName?: string; + availableScopes?: string[]; + resourceScopes?: string[]; + clientRoles?: string[]; + clientMappers?: string[]; + inheritFrom?: undefinedRefID; + owner?: undefinedRefID; +} + + +/** + * @tsoaModel + * @example { + * "environment": "dev", + * "issuerUrl": "https://idp.site/auth/realms/my-realm", + * "clientRegistration": "managed", + * "clientId": "a-client-id", + * "clientSecret": "a-client-secret" + * } + */ +export interface IssuerEnvironmentConfig { + environment?: string; // Primary Key + exists?: boolean; + issuerUrl?: string; + clientRegistration?: "anonymous" | "managed" | "iat"; + clientId?: string; + clientSecret?: string; + initialAccessToken?: string; +} + + +/** + * @tsoaModel + * @example { + * "externalLink": "https://externalsite/my_content", + * "title": "my_content", + * "description": "Summary of what my content is", + * "content": "Markdown content", + * "order": 0, + * "isPublic": true, + * "isComplete": true, + * "tags": [ + * "tag1", + * "tag2" + * ] + * } + */ +export interface Content { + externalLink?: string; // Primary Key + title?: string; + description?: string; + content?: string; + githubRepository?: string; + readme?: string; + order?: number; + isPublic?: boolean; + isComplete?: boolean; + gatewayId?: string; + publishDate?: string; + slug?: string; + tags?: string[]; +} + + +/** + * @tsoaModel + * + */ +export interface ContentBySlug { + slug?: string; // Primary Key + externalLink?: string; + title?: string; + description?: string; + content?: string; + githubRepository?: string; + readme?: string; + order?: string; + isPublic?: string; + isComplete?: string; + gatewayId?: string; + publishDate?: string; + tags?: string[]; +} + + +/** + * @tsoaModel + * + */ +export interface Legal { + reference?: string; // Primary Key + title?: string; + link?: string; + document?: string; + version?: string; + active?: string; +} + + +/** + * @tsoaModel + * @example { + * "type": "Namespace", + * "name": "delete Namespace[ns_x]", + * "action": "delete", + * "refId": "ns_x", + * "result": "success", + * "message": "Deleted ns_x namespace", + * "actor": { + * "name": "XT:Blink, James CITZ:IN" + * }, + * "blob": {}, + * "createdAt": "2022-03-11T00:47:42.947Z", + * "updatedAt": "2022-03-11T00:47:42.947Z" + * } + */ +export interface Activity { + extRefId?: string; // Primary Key + type?: string; + name?: string; + action?: "add" | "update" | "create" | "delete" | "validate" | "publish"; + result?: "" | "received" | "failed" | "completed" | "success"; + message?: string; + refId?: string; + gatewayId?: string; + blob?: string; + filterKey1?: string; + filterKey2?: string; + filterKey3?: string; + filterKey4?: string; + updatedAt?: DateTime; + createdAt?: DateTime; + context?: any; // toString + actor?: UserRefID; +} + + +/** + * @tsoaModel + * + */ +export interface User { + username?: string; // Primary Key + name?: string; + email?: string; + legalsAgreed?: UserLegalsAgreed[]; + provider?: string; +} + + +/** + * @tsoaModel + * + */ +export interface UserLegalsAgreed { + reference?: string; // Primary Key + agreedTimestamp?: string; +} + + +/** + * @tsoaModel + * + */ +export interface Blob { + ref?: string; // Primary Key + type?: string; + blob?: string; +} + + +/** + * @tsoaModel + * + */ +export interface DatasetContact { + name?: string; // Primary Key + email?: string; + role?: "pointOfContact"; +} + + +/** + * @tsoaModel + * + */ +export interface DatasetResource { + id?: string; // Primary Key + name?: string; + format?: "openapi-json" | "json"; + url?: string; +} + +/** + * @tsoaModel + */ +export type ApplicationRefID = string + +/** + * @tsoaModel + */ +export type CredentialIssuerRefID = string + +/** + * @tsoaModel + */ +export type DraftDatasetRefID = string + +/** + * @tsoaModel + */ +export type EnvironmentRefID = string + +/** + * @tsoaModel + */ +export type GatewayConsumerRefID = string + +/** + * @tsoaModel + */ +export type GatewayRouteRefID = string + +/** + * @tsoaModel + */ +export type GatewayServiceRefID = string + +/** + * @tsoaModel + */ +export type LegalRefID = string + +/** + * @tsoaModel + */ +export type OrganizationRefID = string + +/** + * @tsoaModel + */ +export type OrganizationUnitRefID = string + +/** + * @tsoaModel + */ +export type UserRefID = string + +/** + * @tsoaModel + */ +export type undefinedRefID = string diff --git a/src/lists/extensions/Namespace.ts b/src/lists/extensions/Namespace.ts index c680dd23b..85b5565a7 100644 --- a/src/lists/extensions/Namespace.ts +++ b/src/lists/extensions/Namespace.ts @@ -45,7 +45,10 @@ import { getAllNamespaces, getKeycloakGroupApi, getResource, + generateDisplayName, transformOrgAndOrgUnit, + validateDisplayName, + validateNamespaceName, } from '../../services/keycloak/namespace-details'; import { newNamespaceID } from '../../services/identifiers'; @@ -144,6 +147,8 @@ module.exports = { const resource: any = await getResource(selectedNS, envCtx); merged['id'] = resource['id']; merged['scopes'] = resource['scopes']; + merged['displayName'] = + resource['displayName'] || `Gateway ${resource['name']}`; } if (merged.org) { @@ -246,7 +251,7 @@ module.exports = { assert.strictEqual( re.test(args.namespace), true, - 'Namespace name must be between 5 and 15 alpha-numeric lowercase characters and begin with an alphabet.' + 'Gateway name must be between 5 and 15 alpha-numeric lowercase characters and begin with an alphabet.' ); const noauthContext = context.createContext({ skipAccessControl: true, @@ -341,6 +346,41 @@ module.exports = { return true; }, }, + { + schema: + 'updateCurrentNamespaceDisplayName(displayName: String): String', + resolver: async ( + item: any, + { displayName }: any, + context: any, + info: any, + { query, access }: any + ): Promise => { + if ( + context.req.user?.namespace == null || + typeof context.req.user?.namespace === 'undefined' + ) { + return null; + } + + const ns = context.req.user?.namespace; + + validateDisplayName(displayName); + + const prodEnv = await getGwaProductEnvironment(context, true); + + await getNamespaceResourceSets(prodEnv); // sets accessToken + + const resourcesApi = new UMAResourceRegistrationService( + prodEnv.uma2.resource_registration_endpoint, + prodEnv.accessToken + ); + + await resourcesApi.updateDisplayName(ns, displayName); + return true; + }, + access: EnforcementPoint, + }, { schema: 'updateCurrentNamespace(org: String, orgUnit: String): String', @@ -416,6 +456,7 @@ module.exports = { }); } }, + access: EnforcementPoint, }, { schema: @@ -427,15 +468,14 @@ module.exports = { info: any, { query, access }: any ) => { - const namespaceValidationRule = '^[a-z][a-z0-9-]{3,13}[a-z0-9]$'; - const newNS = args.name ? args.name : newNamespaceID(); - regExprValidation( - namespaceValidationRule, - newNS, - 'Namespace name must be between 5 and 15 alpha-numeric lowercase characters and start and end with an alphabet.' - ); + validateNamespaceName(newNS); + + const displayName = + args.displayName || generateDisplayName(context, newNS); + + validateDisplayName(displayName); const noauthContext = context.createContext({ skipAccessControl: true, @@ -478,7 +518,7 @@ module.exports = { ]; const res = { name: newNS, - displayName: args.displayName, + displayName, type: 'namespace', resource_scopes: scopes, ownerManagedAccess: true, @@ -495,6 +535,7 @@ module.exports = { 'Namespace.Manage', 'CredentialIssuer.Admin', 'GatewayConfig.Publish', + 'Access.Manage', ]) { await permissionApi.createPermission( rset.id, diff --git a/src/mocks/handlers.js b/src/mocks/handlers.js index 40ec6ddc8..67fecb014 100644 --- a/src/mocks/handlers.js +++ b/src/mocks/handlers.js @@ -89,12 +89,14 @@ const allNamespaces = [ { id: 'n1', name: 'aps-portal', + displayName: 'API Services Portal gw', orgEnabled: true, createdAt: subDays(new Date(), 20).toISOString(), }, { id: 'n2', name: 'loc', + displayName: 'Location services', orgEnabled: false, createdAt: subDays(new Date(), 5).toISOString(), }, diff --git a/src/mocks/resolvers/namespace-access.js b/src/mocks/resolvers/namespace-access.js index b3828a257..f561fa06d 100644 --- a/src/mocks/resolvers/namespace-access.js +++ b/src/mocks/resolvers/namespace-access.js @@ -131,6 +131,7 @@ let umaPolicies = [ let currentNamespace = { id: 'ns1', name: 'aps-portal', + displayName: 'API Services Portal gw', scopes: [ { name: 'GatewayConfig.Publish' }, { name: 'Namespace.Manage' }, @@ -172,6 +173,7 @@ export const updateCurrentNamesSpaceHandler = (req, res, ctx) => { ...req.variables, org: req.variables.org ? { title: req.variables.org } : null, orgUnit: req.variables.orgUnit ? { title: req.variables.orgUnit } : null, + displayName: req.variables.displayName ? { title: req.variables.displayName } : null, }; return res(ctx.data({})); }; diff --git a/src/nextapp/.env.local b/src/nextapp/.env.local index 31bf55355..e69de29bb 100644 --- a/src/nextapp/.env.local +++ b/src/nextapp/.env.local @@ -1,11 +0,0 @@ -NEXT_PUBLIC_APP_VERSION=0.0.0 -NEXT_PUBLIC_APP_REVISION=000000000000000000000000 -NEXT_PUBLIC_KUBE_CLUSTER=local -NEXT_PUBLIC_HELP_DESK_URL=https://dpdd.atlassian.net/servicedesk/customer/portal/1/group/2 -NEXT_PUBLIC_HELP_CHAT_URL=https://chat.developer.gov.bc.ca/channel/aps-ops -NEXT_PUBLIC_HELP_ISSUE_URL=https://github.com/bcgov/api-services-portal/issues -NEXT_PUBLIC_HELP_API_DOCS_URL=/ds/api/v2/console/ -NEXT_PUBLIC_HELP_SUPPORT_URL=https://developer.gov.bc.ca/docs/default/component/aps-infra-platform-docs/ -NEXT_PUBLIC_HELP_RELEASE_URL=https://developer.gov.bc.ca/docs/default/component/aps-infra-platform-docs/reference/releases/ -NEXT_PUBLIC_HELP_STATUS_URL=https://uptime.com/s/bcgov-dss - diff --git a/src/nextapp/components/activity-item/activity-item.tsx b/src/nextapp/components/activity-item/activity-item.tsx index a09256e89..3f02658a8 100644 --- a/src/nextapp/components/activity-item/activity-item.tsx +++ b/src/nextapp/components/activity-item/activity-item.tsx @@ -173,5 +173,5 @@ function template(string: string, data: TemplateMap): string { } return result || ''; - }); + }).replace(/namespace/gi, (match) => match === 'namespace' ? 'gateway' : 'Gateway') // Replace both 'namespace' and 'Namespace' } diff --git a/src/nextapp/components/auth-action/auth-action.tsx b/src/nextapp/components/auth-action/auth-action.tsx index db3483799..e62271072 100644 --- a/src/nextapp/components/auth-action/auth-action.tsx +++ b/src/nextapp/components/auth-action/auth-action.tsx @@ -24,7 +24,6 @@ import { makeRedirectUrl, useAuth, } from '@/shared/services/auth'; -import NamespaceMenu from '../namespace-menu'; import HelpMenu from './help-menu'; import NextLink from 'next/link'; import { useGlobal } from '@/shared/services/global'; @@ -45,7 +44,7 @@ const Signin: React.FC = ({ site }) => { const providerUrl = React.useMemo(() => { const providerUrl = new URL('/login', location.origin); providerUrl.searchParams.set('identity', 'provider'); - const f = makeRedirectUrl(router.asPath); + const f = makeRedirectUrl(router.asPath, 'provider'); providerUrl.searchParams.set('f', f); return providerUrl; }, []); @@ -67,7 +66,6 @@ const Signin: React.FC = ({ site }) => { return ( - = ({ site }) => { return ( - } spacing={4} > - {user.roles.includes('portal-user') && } = ({ site }) => { { > = ({ id, title, description, command }) => { + const toast = useToast(); + const handleClipboard = React.useCallback( + (text: string) => async () => { + await navigator.clipboard.writeText(text); + toast({ + title: 'Copied to clipboard!', + status: 'success', + }); + }, + [toast] + ); + + return ( + + {title} + {description} + {command && ( + + {command} + + } + fontSize='20px' + onClick={handleClipboard(command)} + /> + + + )} + + ); +}; + +export default CliCommand; diff --git a/src/nextapp/components/cli-command/index.ts b/src/nextapp/components/cli-command/index.ts new file mode 100644 index 000000000..27935a223 --- /dev/null +++ b/src/nextapp/components/cli-command/index.ts @@ -0,0 +1 @@ +export { default } from './cli-command'; diff --git a/src/nextapp/components/discovery-list/discovery-list-item.tsx b/src/nextapp/components/discovery-list/discovery-list-item.tsx index 3518ef385..bf9473315 100644 --- a/src/nextapp/components/discovery-list/discovery-list-item.tsx +++ b/src/nextapp/components/discovery-list/discovery-list-item.tsx @@ -36,6 +36,12 @@ enum EnvironmentOrder { other, } +// Utility function to get the first sentence of dataset description +const getFirstSentence = (text) => { + const firstPeriodIndex = text.indexOf('.'); + return firstPeriodIndex !== -1 ? text.substring(0, firstPeriodIndex + 1) : text; +}; + const DiscoveryListItem: React.FC = ({ data, preview, @@ -112,9 +118,9 @@ const DiscoveryListItem: React.FC = ({ {!data.organization && 'Open Dataset'} {data && ( - - {data.notes} - + + {getFirstSentence(data.notes)} + )} {!data && ( diff --git a/src/nextapp/components/edit-display-name/edit-display-name.tsx b/src/nextapp/components/edit-display-name/edit-display-name.tsx new file mode 100644 index 000000000..061bb1c4d --- /dev/null +++ b/src/nextapp/components/edit-display-name/edit-display-name.tsx @@ -0,0 +1,194 @@ +import * as React from 'react'; +import { + Button, + ButtonGroup, + FormControl, + FormHelperText, + FormLabel, + Icon, + Input, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, + useDisclosure, + useToast, +} from '@chakra-ui/react'; +import { FaPen } from 'react-icons/fa'; +import { QueryKey, useQueryClient } from 'react-query'; +import { useApiMutation } from '@/shared/services/api'; +import { gql } from 'graphql-request'; +import { NamespaceInput } from '@/shared/types/query.types'; + +interface EditNamespaceDisplayNameProps { + data: NamespaceInput; + queryKey: QueryKey; +} + +const EditNamespaceDisplayName: React.FC = ({ + data, + queryKey, +}) => { + const toast = useToast(); + const { isOpen, onOpen, onClose: originalOnClose } = useDisclosure(); + const queryClient = useQueryClient(); + const mutate = useApiMutation(mutation); + const [inputValue, setInputValue] = React.useState(data.displayName || ''); + const [charCount, setCharCount] = React.useState( + data.displayName?.length || 0 + ); + const minCharLimit = 3; + const maxCharLimit = 30; + const [isValidFormat, setIsValidFormat] = React.useState(true); + const validNameRegex = /^[a-zA-Z0-9][\w\s().'/-]*$/; + + const handleInputChange = (event) => { + const { value } = event.target; + setInputValue(value); + setCharCount(value.length); + setIsValidFormat(validNameRegex.test(value)); + }; + + const form = React.useRef(); + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (charCount >= minCharLimit && charCount <= maxCharLimit && isValidFormat) { + updateNamespaceDisplayName(); + } + }; + + const updateNamespaceDisplayName = async () => { + if (form.current) { + try { + if (form.current.checkValidity()) { + const formData = new FormData(form.current); + const entries = Object.fromEntries(formData); + await mutate.mutateAsync(entries); + queryClient.invalidateQueries(queryKey); + originalOnClose(); + toast({ + title: 'Display name successfully edited', + status: 'success', + isClosable: true, + }); + } + } catch (err) { + toast({ + title: 'Display name update failed', + description: err, + status: 'error', + isClosable: true, + }); + } + } + }; + + const handleSaveClick = () => { + form.current?.requestSubmit(); + }; + + const isInputValid = charCount >= minCharLimit && charCount <= maxCharLimit && isValidFormat; + + // Add this new function to handle closing and resetting + const handleClose = () => { + setInputValue(data.displayName || ''); + setCharCount(data.displayName?.length || 0); + setIsValidFormat(true); + originalOnClose(); + }; + + return ( + <> + + + + + Edit display name + +
+ + + + A meaningful display name makes it easy for anyone to identify + and distinguish this Gateway from others. + + maxCharLimit || charCount < minCharLimit ? 'bc-error' : 'gray.500'} + mt={2} + textAlign="right" + > + {charCount}/{maxCharLimit} + + + {charCount > maxCharLimit && ( + + Display name must be less than 30 characters + + )} + {charCount < minCharLimit && ( + + Display name must be at least 3 characters + + )} + {!isValidFormat && (charCount >= minCharLimit) && (charCount <= maxCharLimit) && ( + + Display name must start with an alphanumeric character and can only use special characters "-()_ .'/" + + )} + +
+
+ + + + + + +
+
+ + ); +}; + +export default EditNamespaceDisplayName; + +const mutation = gql` + mutation UpdateNamespaceDisplayName($displayName: String!) { + updateCurrentNamespaceDisplayName(displayName: $displayName) + } +`; diff --git a/src/nextapp/components/edit-display-name/index.ts b/src/nextapp/components/edit-display-name/index.ts new file mode 100644 index 000000000..ab9c667a2 --- /dev/null +++ b/src/nextapp/components/edit-display-name/index.ts @@ -0,0 +1 @@ +export { default } from './edit-display-name'; diff --git a/src/nextapp/components/environment-edit/authorization-flow.tsx b/src/nextapp/components/environment-edit/authorization-flow.tsx index f87345ab1..f6b0f5469 100644 --- a/src/nextapp/components/environment-edit/authorization-flow.tsx +++ b/src/nextapp/components/environment-edit/authorization-flow.tsx @@ -141,7 +141,7 @@ const AuthorizationFlow: React.FC = ({ { + event.preventDefault(); + const targetId = href.substring(1); // Remove the # from href to get the target id + const target = document.getElementById(targetId); + if (target) { + const yOffset = -120; // You may adjust this value to offset the scroll position if needed + const y = target.getBoundingClientRect().top + window.pageYOffset + yOffset; + window.scrollTo({ top: y, behavior: "smooth" }); + } + }; + + return ( + + {children} + + ) +} +const GatewayGetStarted: React.FC = ({ + hasNamespaces +}) => { + const global = useGlobal(); + const GlossaryUrl = global?.helpLinks.helpSupportUrl + 'reference/glossary' + const QuickStartUrl = global?.helpLinks.helpSupportUrl + 'tutorials/quick-start' + const ClientCredTutorialUrl = global?.helpLinks.helpSupportUrl + 'tutorials/protect-client-cred' + const GwaInstallUrl = global?.helpLinks.helpSupportUrl + 'how-to/gwa-install' + const GwaCommandsUrl = global?.helpLinks.helpSupportUrl + 'reference/gwa-commands' + const HelpDeskURL = global?.helpLinks.helpDeskUrl + const HelpChatURL = global?.helpLinks.helpChatUrl + const apiRootUrl = global?.apiRootUrl + const configHost = apiRootUrl ? apiRootUrl.replace('https://', '').replace('http://', '') : '' + + return ( + <> + + {!hasNamespaces && ( + + Empty folder + No Gateways created yet + + )} + What is a Gateway? + + A Gateway acts as a central entry point for multiple APIs. Its main purpose is to facilitate communication and control the data flow between your APIs and those who consume them. + + + After your first Gateway is created, in this section you can do things like: + + + + + + + Make your products discoverable + + + + Allow citizens to find your APIs on the BC Government API Directory. + + + + + + + Control access to your products + + + + Decide who can use the data and how they access it. + + + + + + + View usage metrics + + + + Get a fast overview of how much and how often the services you've set up are being used. + + + + + + + Steps to create and configure your first Gateway + + Follow these steps to create and configure your first Gateway. For more detailed instructions on setting up an API, consult our{' '} + Quick Start tutorial + . + + + + + + + + + Download + + + + Download our Command Line Interface (CLI) + + + Our gwa CLI allows you to configure your Gateways.{' '} + Download it here. + + + + + + + + + Configuration + + + + Prepare your configuration + + + Create a Gateway + {' '} and use our{' '} + template + {' '}to set up service and route configuration. + + + + + + + + + Apply configuration + + + + Apply configuration to your Gateway + + + Run the{' '} + apply command + {' '}in the CLI to apply your configuration to your Gateway. + + + + + + Glossary search + + New terminology? + + + + Explore our glossary + + {' '}for easy-to-understand definitions + + + + Prepare your configuration + {global?.apiRootUrl && ( + + )} + + + + Run this command to generate a basic Gateway configuration YAML file from a template. +

+ First, choose a unique name for your API service to be shown as part of your vanity URL: <MYSERVICE>.dev.api.gov.bc.ca. + Then run the following command, substituting your service name for <MYSERVICE>: + + } + command='gwa generate-config --template quick-start --service --upstream https://httpbin.org' + /> + + Apply configuration to your Gateway + + + Get the URL for your newly published Gateway Service <MYSERVICE>.dev.api.gov.bc.ca using this command. + Visit the URL in a browser to see your API gateway in action. + + } + command='gwa status --hosts' + /> + + On the API Services Portal, go to the Gateways tab + and select your newly created Gateway. In the Products panel, + click the Preview in Directory link. +

+ You will see a card with the service name you chose earlier (<MYSERVICE>). + This card is a preview of how the service would look when shared to the API Directory. + + } + command='' + /> + + Next steps + + Congratulations! You have set up your first Gateway Service, creating a custom route to your service through your API gateway, along with a listing in the API Directory. + + + For a deeper introduction to the API Services Portal, follow the {' '} + Protect an API with Client Credential Flow tutorial + {' '}to create a protected API. + + + + Help + + If you are not sure about how to use a specific gwa command, you can type --help after the + command's name to learn more about its usage and syntax. + Alternatively, view the {' '} + CLI reference documentation. + + } + command='gwa --help' + /> + + Join the{' '} + #aps-ops channel + {' '} on Rocket.Chat to connect with our team and user community. + Alternatively, {' '} + open a support ticket + {' '} and we'll get back to you via email in 3-5 business days. + Either way, our team is here to answer your questions. + + } + command='' + /> +
+
+ + ); +}; + +export default GatewayGetStarted; diff --git a/src/nextapp/components/gateway-get-started/index.ts b/src/nextapp/components/gateway-get-started/index.ts new file mode 100644 index 000000000..92a469558 --- /dev/null +++ b/src/nextapp/components/gateway-get-started/index.ts @@ -0,0 +1 @@ +export { default } from './gateway-get-started'; diff --git a/src/nextapp/components/inline-permissions-list/inline-permissions-list.tsx b/src/nextapp/components/inline-permissions-list/inline-permissions-list.tsx index 9454a4dc8..c31d9650b 100644 --- a/src/nextapp/components/inline-permissions-list/inline-permissions-list.tsx +++ b/src/nextapp/components/inline-permissions-list/inline-permissions-list.tsx @@ -27,7 +27,7 @@ const InlinePermissionsList: React.FC = ({ {data.map((p) => ( - {p.scopeName} + {p.scopeName.replace(/Namespace/g, 'Gateway')} {enableRevoke && } diff --git a/src/nextapp/components/link-consumer/link-consumer.tsx b/src/nextapp/components/link-consumer/link-consumer.tsx index 5e5a764d4..4e6bed450 100644 --- a/src/nextapp/components/link-consumer/link-consumer.tsx +++ b/src/nextapp/components/link-consumer/link-consumer.tsx @@ -38,7 +38,7 @@ const LinkConsumerDialog: React.FC = ({ const client = useQueryClient(); const link = useApiMutation(mutation); const toast = useToast(); - const title = 'Link Consumer to Namespace'; + const title = 'Link Consumer to Gateway'; const formRef = React.useRef(); const { isOpen, onClose, onOpen } = useDisclosure(); const handleLink = async () => { diff --git a/src/nextapp/components/namespace-access/namespace-access-dialog.tsx b/src/nextapp/components/namespace-access/namespace-access-dialog.tsx index 46e8ad2bd..5637a864e 100644 --- a/src/nextapp/components/namespace-access/namespace-access-dialog.tsx +++ b/src/nextapp/components/namespace-access/namespace-access-dialog.tsx @@ -153,7 +153,7 @@ const NamespaceAccessDialog: React.FC = ({ }} isChecked={selected.includes(r.name)} > - {r.name} + {r.name.replace(/Namespace/g, 'Gateway')}
{permissionHelpTextLookup[r.name] ?? ''} @@ -201,6 +201,6 @@ const permissionHelpTextLookup = { 'GatewayConfig.Publish': 'Can publish gateway configuration to Kong and to view the status of the upstreams.', 'Namespace.Manage': - 'Can update the Access Control List for controlling access to viewing metrics, service configuration and service account management. This is a superuser for the Namespace.', - 'Namespace.View': 'Read-only access to the namespace.', + 'Can update the Access Control List for controlling access to viewing metrics, service configuration and service account management. This is a superuser for the gateway.', + 'Namespace.View': 'Read-only access to the gateway.', }; diff --git a/src/nextapp/components/namespace-access/organization-groups-access.tsx b/src/nextapp/components/namespace-access/organization-groups-access.tsx index 61eef69b0..84c537e15 100644 --- a/src/nextapp/components/namespace-access/organization-groups-access.tsx +++ b/src/nextapp/components/namespace-access/organization-groups-access.tsx @@ -136,7 +136,7 @@ const OrganizationGroupsAccess: React.FC = ({ {d.scopes.map((t) => ( - {t} + {t.replace(/Namespace/g, 'Gateway')} ))} diff --git a/src/nextapp/components/namespace-access/service-accounts-access.tsx b/src/nextapp/components/namespace-access/service-accounts-access.tsx index 6d8746967..9b7c343bf 100644 --- a/src/nextapp/components/namespace-access/service-accounts-access.tsx +++ b/src/nextapp/components/namespace-access/service-accounts-access.tsx @@ -184,7 +184,7 @@ const ServiceAccountsAccess: React.FC = ({ )} - {requests?.length ?? '0'} service accounts + {requests?.length ?? '0'} service account{requests?.length > 1 && 's'} = ({ {d.scopes.map((t) => ( - {t} + {t.replace(/Namespace/g, 'Gateway')} ))} diff --git a/src/nextapp/components/namespace-access/users-access.tsx b/src/nextapp/components/namespace-access/users-access.tsx index 34224e2b5..fd896ba62 100644 --- a/src/nextapp/components/namespace-access/users-access.tsx +++ b/src/nextapp/components/namespace-access/users-access.tsx @@ -265,7 +265,7 @@ const UsersAccess: React.FC = ({ {d.scopes.map((s) => ( - {s.name} + {s.name.replace(/Namespace/g, 'Gateway')} ))} diff --git a/src/nextapp/components/namespace-actions/namespace-actions.tsx b/src/nextapp/components/namespace-actions/namespace-actions.tsx index d91be009a..2cae51e9f 100644 --- a/src/nextapp/components/namespace-actions/namespace-actions.tsx +++ b/src/nextapp/components/namespace-actions/namespace-actions.tsx @@ -33,13 +33,13 @@ const NamespaceActions: React.FC = ({ name }) => { } variant="primary" /> } onClick={handleDelete}> - Delete Namespace... + Delete Gateway... diff --git a/src/nextapp/components/namespace-delete/namespace-delete.tsx b/src/nextapp/components/namespace-delete/namespace-delete.tsx index b563a031d..b93823edb 100644 --- a/src/nextapp/components/namespace-delete/namespace-delete.tsx +++ b/src/nextapp/components/namespace-delete/namespace-delete.tsx @@ -36,6 +36,11 @@ const NamespaceDelete: React.FC = ({ const deleteMutation = useApiMutation(mutation); const handleDelete = React.useCallback(async () => { + toast({ + title: `Deleting gateway: ${name}`, + status: 'info', + isClosable: true, + }); try { await deleteMutation.mutateAsync({ name, force }); @@ -45,7 +50,7 @@ const NamespaceDelete: React.FC = ({ } toast({ - title: ' Namespace deleted', + title: 'Gateway deleted', status: 'success', isClosable: true, }); @@ -53,7 +58,7 @@ const NamespaceDelete: React.FC = ({ onClose(); } catch (err) { toast({ - title: 'Namespace delete failed', + title: 'Gateway delete failed', description: err, status: 'error', isClosable: true, @@ -76,10 +81,10 @@ const NamespaceDelete: React.FC = ({ > - {`Delete ${name} Namespace`} + {`Delete ${name} Gateway`} - {`Are you sure you want to delete the ${name} namespace? It cannot be undone.`} + {`Are you sure you want to delete the ${name} gateway? It cannot be undone.`} diff --git a/src/nextapp/components/new-organization-form/new-organization-form.tsx b/src/nextapp/components/new-organization-form/new-organization-form.tsx index 725cf4ab6..5f77c1eb2 100644 --- a/src/nextapp/components/new-organization-form/new-organization-form.tsx +++ b/src/nextapp/components/new-organization-form/new-organization-form.tsx @@ -63,12 +63,12 @@ const NewOrganizationForm: React.FC = () => { onClose(); toast({ status: 'success', - title: 'Namespace updated', + title: 'Gateway updated', }); } catch (err) { toast({ status: 'error', - title: 'Namespace update failed', + title: 'Gateway update failed', description: err, }); } @@ -88,9 +88,9 @@ const NewOrganizationForm: React.FC = () => { - Adding your Organization and Business Unit to your namespace will + Adding your Organization and Business Unit to your gateway will notify the Organization Administrator to enable API publishing to - the Directory for your namespace so consumers can find and request + the Directory for your gateway so consumers can find and request access to your APIs. diff --git a/src/nextapp/components/new-product/new-product-dialog.tsx b/src/nextapp/components/new-product/new-product-dialog.tsx index 6bdef37ae..44e636cd1 100644 --- a/src/nextapp/components/new-product/new-product-dialog.tsx +++ b/src/nextapp/components/new-product/new-product-dialog.tsx @@ -65,10 +65,11 @@ const NewProductDialog: React.FC = ({ isClosable: true, }); onClose(); - } catch { + } catch (err: any) { toast({ title: 'Create failed', status: 'error', + description: err, isClosable: true, }); } diff --git a/src/nextapp/components/ns-breadcrumb/ns-breadcrumb.tsx b/src/nextapp/components/ns-breadcrumb/ns-breadcrumb.tsx index fa2166110..9ae298de3 100644 --- a/src/nextapp/components/ns-breadcrumb/ns-breadcrumb.tsx +++ b/src/nextapp/components/ns-breadcrumb/ns-breadcrumb.tsx @@ -7,7 +7,7 @@ const Breadcrumb = (crumbs = []) => { return user ? [ - { href: '/manager/namespaces', text: `Namespaces (${user.namespace})` }, + { href: '/manager/gateways', text: `Gateways (${user.namespace})` }, ].concat(crumbs) : []; }; diff --git a/src/nextapp/components/page-header/page-header.tsx b/src/nextapp/components/page-header/page-header.tsx index 57e097578..163a2b236 100644 --- a/src/nextapp/components/page-header/page-header.tsx +++ b/src/nextapp/components/page-header/page-header.tsx @@ -15,6 +15,7 @@ interface PageHeaderProps { breadcrumb?: { href?: string; text: string }[]; children?: React.ReactNode; title: React.ReactNode; + apiDirectoryNav?: boolean; } const PageHeader: React.FC = ({ @@ -22,9 +23,10 @@ const PageHeader: React.FC = ({ breadcrumb, children, title, + apiDirectoryNav }) => { return ( - + {breadcrumb && ( diff --git a/src/nextapp/components/preview-banner/preview-banner.tsx b/src/nextapp/components/preview-banner/preview-banner.tsx index dacbc1fa1..89b71ac38 100644 --- a/src/nextapp/components/preview-banner/preview-banner.tsx +++ b/src/nextapp/components/preview-banner/preview-banner.tsx @@ -82,7 +82,7 @@ const PreviewBanner: React.FC = () => { {`Your Organization Administrator has been notified to enable API - Publishing to the Directory for the ${user.namespace} namespace.`} + Publishing to the Directory for the gateway ${data?.currentNamespace?.displayName}.`} - - - - ); -}; - -export default ResourcesManagerDialog; diff --git a/src/nextapp/components/resources-manager/resources-manager.tsx b/src/nextapp/components/resources-manager/resources-manager.tsx deleted file mode 100644 index f4ab00202..000000000 --- a/src/nextapp/components/resources-manager/resources-manager.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import * as React from 'react'; -import { Badge, Button, Icon, useDisclosure } from '@chakra-ui/react'; -import ResourcesManagerDialog from './resources-manager-dialog'; -import { UmaPermissionTicket } from '@/shared/types/query.types'; -import { FaUserPlus } from 'react-icons/fa'; -import { QueryKey } from 'react-query'; - -interface ResourcesManagerProps { - prodEnvId: string; - data: UmaPermissionTicket[]; - queryKey: QueryKey; - resourceId: string; -} - -const ResourcesManager: React.FC = ({ - prodEnvId, - data, - queryKey, - resourceId, -}) => { - const { isOpen, onClose, onOpen } = useDisclosure(); - - return ( - <> - - - - ); -}; - -export default ResourcesManager; diff --git a/src/nextapp/components/service-account-create/service-account-create.tsx b/src/nextapp/components/service-account-create/service-account-create.tsx index 32b83c079..652e92e71 100644 --- a/src/nextapp/components/service-account-create/service-account-create.tsx +++ b/src/nextapp/components/service-account-create/service-account-create.tsx @@ -118,7 +118,7 @@ const ServiceAccountCreate: React.FC = ({ {data.currentNamespace?.scopes?.map((s) => ( - {s.name} + {s.name.replace(/Namespace/g, 'Gateway')} ))} diff --git a/src/nextapp/components/users-access-list/index.ts b/src/nextapp/components/users-access-list/index.ts deleted file mode 100644 index 4f69abfd4..000000000 --- a/src/nextapp/components/users-access-list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './users-access-list'; diff --git a/src/nextapp/components/users-access-list/resources-list-item.tsx b/src/nextapp/components/users-access-list/resources-list-item.tsx deleted file mode 100644 index e4e9168e4..000000000 --- a/src/nextapp/components/users-access-list/resources-list-item.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react'; -import { - Avatar, - Flex, - HStack, - Tag, - TagCloseButton, - Td, - Text, - Tr, -} from '@chakra-ui/react'; - -interface ResourcesListItemProps { - resourceId: string; -} - -const ResourcesListItem: React.FC = ({ - resourceId, -}) => { - return ( - - - - - - JOSHJONE - - - - - - {['api-owner', 'credential-admin'].map((d) => ( - - {d} - - - ))} - - - - - ); -}; - -export default ResourcesListItem; diff --git a/src/nextapp/components/users-access-list/users-access-list.tsx b/src/nextapp/components/users-access-list/users-access-list.tsx deleted file mode 100644 index 5903babe0..000000000 --- a/src/nextapp/components/users-access-list/users-access-list.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import * as React from 'react'; -import { - Avatar, - Button, - Flex, - Icon, - Table, - Tbody, - Td, - Text, - Th, - Thead, - Tr, - useToast, -} from '@chakra-ui/react'; -import flow from 'lodash/flow'; -import groupBy from 'lodash/groupBy'; -import { gql } from 'graphql-request'; -import head from 'lodash/head'; -import uniq from 'lodash/uniq'; -import type { UmaPermissionTicket } from '@/types/query.types'; - -import InlinePermissionsList from '../inline-permissions-list'; -import { QueryKey, useQueryClient } from 'react-query'; -import { useApiMutation } from '@/shared/services/api'; -import { FaCheck, FaMinusCircle } from 'react-icons/fa'; - -interface PermissionItem { - id: string; - scope: string; - scopeName: string; -} - -interface UserItem { - username: string; - permissions: PermissionItem[]; -} - -interface RevokeVariables { - prodEnvId: string; - tickets: string[]; -} - -interface UsersAccessListProps { - prodEnvId: string; - data: UmaPermissionTicket[]; - enableRevoke?: boolean; - resourceId: string; - queryKey: QueryKey; -} - -const UsersAccessList: React.FC = ({ - prodEnvId, - data, - enableRevoke, - resourceId, - queryKey, -}) => { - const toast = useToast(); - const client = useQueryClient(); - const grant = useApiMutation<{ - prodEnvId: string; - resourceId: string; - requesterId: string; - scopes: string[]; - }>(grantMutation); - const revoke = useApiMutation(revokeMutation); - const groupedByRequester = groupBy(data, 'requester'); - const users = React.useMemo(() => { - const result = []; - const usernames = Object.keys(groupedByRequester); - - usernames.forEach((u) => { - const permissions = groupedByRequester[u]; - const username = getUserName(permissions); - - result.push({ - id: u, - username, - permissions: permissions.map((p) => ({ - id: p.id, - scope: p.scope, - scopeName: p.scopeName, - })), - }); - }); - - return result; - }, [groupedByRequester]); - - // Events - const handleGrant = (permissions: PermissionItem[]) => async () => { - try { - const payload = { - prodEnvId, - resourceId, - requesterId: '123', - scopes: permissions.map((p) => p.id), - }; - await grant.mutateAsync(payload); - toast({ - title: 'Access granted', - status: 'success', - isClosable: true, - }); - } catch (err) { - toast({ - title: 'Access granted failed', - status: 'error', - isClosable: true, - }); - } - }; - const handleRevoke = async (id: string | string[]) => { - try { - const tickets = Array.isArray(id) ? id : [id]; - await revoke.mutateAsync({ prodEnvId, resourceId, tickets }); - toast({ - title: 'Access revoked', - status: 'success', - isClosable: true, - }); - client.invalidateQueries(queryKey); - } catch (err) { - toast({ - title: 'Revoke access scope failed', - description: err, - status: 'error', - isClosable: true, - }); - } - }; - const handleRevokeAll = (permissions: PermissionItem[]) => () => { - const ids = permissions.map((p) => p.id); - handleRevoke(ids); - }; - - return ( - - - - - - - - - - {users.map((d) => ( - - - - - - ))} - -
UserPermissionActions
- - - - {d.username} - - - - - - {enableRevoke && ( - - )} - {!enableRevoke && ( - - )} -
- ); -}; - -export default UsersAccessList; - -const revokeMutation = gql` - mutation RevokeAccess( - $prodEnvId: ID! - $resourceId: String! - $tickets: [String]! - ) { - revokePermissions( - prodEnvId: $prodEnvId - resourceId: $resourceId - ids: $tickets - ) - } -`; - -const grantMutation = gql` - mutation GrantAccess( - $prodEnvId: ID! - $resourceId: String! - $requesterId: String! - $scopes: [String]! - ) { - approvePermissions( - prodEnvId: $prodEnvId - resourceId: $resourceId - requesterId: $requesterId - scopes: $scopes - ) - } -`; - -const getUserName = (permissions: UmaPermissionTicket[]): string => { - const parse = flow( - (value: UmaPermissionTicket[]) => value.map((v) => v.requesterName), - (value: string[]) => uniq(value), - (value: string[]) => head(value) - ); - return parse(permissions); -}; diff --git a/src/nextapp/pages/_app.tsx b/src/nextapp/pages/_app.tsx index 3e0a4e26b..f3bb362ba 100644 --- a/src/nextapp/pages/_app.tsx +++ b/src/nextapp/pages/_app.tsx @@ -18,7 +18,7 @@ import Header from '@/components/header'; import NavBar from '@/components/nav-bar'; import MaintenanceBanner from '@/components/maintenance-banner'; import theme from '@/shared/theme'; -import links from '@/shared/data/links'; +import links, { gatewayPages } from '@/shared/data/links'; import AuthAction from '@/components/auth-action'; import { ReactQueryDevtools } from 'react-query/devtools'; import type { AppProps } from 'next/app'; @@ -56,9 +56,6 @@ const App: React.FC = ({ Component, pageProps }) => { const router = useRouter(); const queryClientRef = React.useRef(); const site: string = React.useMemo(() => { - //if (router?.pathname.startsWith('/manager')) { - // return 'manager'; - //} if (router?.pathname.startsWith('/platform')) { return 'platform'; } @@ -69,6 +66,9 @@ const App: React.FC = ({ Component, pageProps }) => { return 'devportal'; }, [router]); + // Temp solution for handing spacing around new gateways dropdown menu + const requiresNamespace = gatewayPages.includes(router?.pathname); + if (!queryClientRef.current) { queryClientRef.current = new QueryClient({ defaultOptions: { @@ -105,7 +105,14 @@ const App: React.FC = ({ Component, pageProps }) => { - + diff --git a/src/nextapp/pages/auth_callback.tsx b/src/nextapp/pages/auth_callback.tsx index 1325fad9d..3d2a51970 100644 --- a/src/nextapp/pages/auth_callback.tsx +++ b/src/nextapp/pages/auth_callback.tsx @@ -4,20 +4,28 @@ import { useRouter } from 'next/router'; import { GetServerSideProps, InferGetServerSidePropsType } from 'next'; export const getServerSideProps: GetServerSideProps = async (context) => { + const { identity } = context.query; + return { - props: {}, + props: { + identity: identity || null, + }, }; }; const AuthCallbackPage: React.FC< InferGetServerSidePropsType -> = () => { +> = ({ identity }) => { const { ok } = useAuth(); const router = useRouter(); React.useEffect(() => { if (ok) { - router.push('/devportal/api-directory'); + if (identity === 'provider') { + router.push('/manager/gateways'); + } else { + router.push('/devportal/api-directory'); + } } else { router.push('/login'); } diff --git a/src/nextapp/pages/devportal/access/[id]/index.tsx b/src/nextapp/pages/devportal/access/[id]/index.tsx deleted file mode 100644 index 5748dc313..000000000 --- a/src/nextapp/pages/devportal/access/[id]/index.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import * as React from 'react'; -import { - Button, - Box, - Container, - Text, - Divider, - Heading, - Wrap, - WrapItem, - Center, -} from '@chakra-ui/react'; -// import EmptyPane from '@/components/empty-pane'; -import Head from 'next/head'; -import PageHeader from '@/components/page-header'; -import { gql } from 'graphql-request'; -import api, { useApi } from '@/shared/services/api'; -import { GetServerSideProps, InferGetServerSidePropsType } from 'next'; -import { QueryClient } from 'react-query'; -import { Environment, Query } from '@/shared/types/query.types'; -import ResourcesList from '@/components/resources-list'; -import { dehydrate } from 'react-query/hydration'; -import { getSession } from '@/shared/services/auth'; -import ClientRequest from '@/components/client-request'; -import ResourcesListLoading from '@/components/resources-list/resources-list-loading'; -import EmptyPane from '@/components/empty-pane'; - -export const getServerSideProps: GetServerSideProps = async (context) => { - const { id } = context.params; - const { headers } = context.req; - const queryClient = new QueryClient(); - const queryKey = ['allProductEnvironments', id]; - const user = await getSession(headers as HeadersInit); - - await queryClient.prefetchQuery( - queryKey, - async () => - await api( - query, - { id }, - { - headers: headers as HeadersInit, - } - ) - ); - - return { - props: { - dehydratedState: dehydrate(queryClient), - id, - user, - queryKey, - }, - }; -}; - -const ApiAccessServicePage: React.FC< - InferGetServerSidePropsType -> = ({ id, user, queryKey }) => { - const { data } = useApi( - queryKey, - { query, variables: { id } }, - { suspense: false } - ); - const defaultEnvironment = data.Product?.environments[0] ?? null; - const [ - selectedEnvironment, - setSelectedEnvironment, - ] = React.useState(defaultEnvironment); - - const handleSelectEnvironment = React.useCallback( - (environment: Environment) => () => { - setSelectedEnvironment(environment); - }, - [setSelectedEnvironment] - ); - - return ( - <> - - {`API Program Services | API Access | ${selectedEnvironment?.name}`} - - - - - - - {`Resources for ${selectedEnvironment?.name}`} - - - {data.Product?.environments.length > 1 && ( - <> - - - Filter by Environment - - {data.Product?.environments - .filter((e) => e.credentialIssuer) - .map((e) => ( - - - - ))} - - - - )} - {selectedEnvironment && - selectedEnvironment.credentialIssuer != null && ( - }> - - - )} - {!selectedEnvironment && ( - - - - )} - - - - - ); -}; - -export default ApiAccessServicePage; - -const query = gql` - query GetEnvironmentsByProduct($id: ID!) { - Product(where: { id: $id }) { - name - environments { - id - name - credentialIssuer { - id - resourceType - } - } - } - } -`; diff --git a/src/nextapp/pages/devportal/access/[id]/resources-not-used.tsx b/src/nextapp/pages/devportal/access/[id]/resources-not-used.tsx deleted file mode 100644 index 4a3781333..000000000 --- a/src/nextapp/pages/devportal/access/[id]/resources-not-used.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import * as React from 'react'; -import { - Alert, - AlertIcon, - Avatar, - AvatarGroup, - Box, - Container, - Divider, - Heading, - Link, - Progress, - Stack, - Table, - Tbody, - Td, - Th, - Thead, - Tooltip, - Tr, -} from '@chakra-ui/react'; -// import EmptyPane from '@/components/empty-pane'; -import Head from 'next/head'; -import PageHeader from '@/components/page-header'; -import { gql } from 'graphql-request'; -import api, { useApi } from '@/shared/services/api'; -import { GetServerSideProps, InferGetServerSidePropsType } from 'next'; -import NextLink from 'next/link'; -import { QueryClient } from 'react-query'; -import { Query } from '@/shared/types/query.types'; -import { dehydrate } from 'react-query/hydration'; -import { getSession, useSession, useAuth } from '@/shared/services/auth'; -import { useRouter } from 'next/router'; - -interface ResourcesProps { - prodEnvId: string; - owner: string; - resourceType: string; - environment: string; -} - -const ResourcesComponent: React.FC = ({prodEnvId, owner, resourceType, environment}) => { - const { data } = useApi( - 'allProductResources', - { query, variables: { prodEnvId, resourceType } }, - { suspense: false } - ); - - return ( - <> - {data?.getResourceSet == null && } - - - Resources for {environment} - - - - - - - - - - - - {data?.allResourceSets?.map((r) => ( - - - - - - ))} - -
Resource ({resourceType})TypeShared With
- - {r.name} - - {r.type} - - - {data.allPermissionTickets - ?.filter((p) => p.resource === r.id) - .map((p) => ( - - ))} - -
-
- - ) -} - - -export default ResourcesComponent; - -const query = gql` - query GetResources($prodEnvId: ID!, $resourceType: String) { - allResourceSets(prodEnvId: $prodEnvId, type: $resourceType) { - id - name - type - } - - allPermissionTickets(prodEnvId: $prodEnvId) { - id - owner - ownerName - requester - requesterName - resource - resourceName - scope - scopeName - granted - } - } -`; diff --git a/src/nextapp/pages/devportal/api-directory/[id].tsx b/src/nextapp/pages/devportal/api-directory/[id].tsx index a1a752859..ab4d30801 100644 --- a/src/nextapp/pages/devportal/api-directory/[id].tsx +++ b/src/nextapp/pages/devportal/api-directory/[id].tsx @@ -48,7 +48,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { preview === 'false' && (await queryClient.prefetchQuery( queryKey, - async () => await restApi(`/ds/api/v2/directory/${id}`) + async () => await restApi(`/ds/api/v3/directory/${id}`) )); return { @@ -68,8 +68,8 @@ const ApiPage: React.FC< const { data } = useQuery(queryKey, () => restApi( preview - ? `/ds/api/v2/namespaces/${user?.namespace}/directory/${id}` - : `/ds/api/v2/directory/${id}` + ? `/ds/api/v3/gateways/${user?.namespace}/directory/${id}` + : `/ds/api/v3/directory/${id}` ) ); const breadcrumb = React.useMemo(() => { @@ -134,7 +134,7 @@ const ApiPage: React.FC< Published by
{data?.organizationUnit && ( - {data.organizationUnit.title} + {data.organizationUnit.title} )}
diff --git a/src/nextapp/pages/devportal/api-directory/your-products.tsx b/src/nextapp/pages/devportal/api-directory/your-products.tsx index 4f2c0033f..fb19b180f 100644 --- a/src/nextapp/pages/devportal/api-directory/your-products.tsx +++ b/src/nextapp/pages/devportal/api-directory/your-products.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import ApiDirectoryNav from '@/components/api-directory-nav'; -import { Box, Button, Container, Text } from '@chakra-ui/react'; +import { Box, Button, Container, Flex, Icon, Skeleton, Text, Tooltip } from '@chakra-ui/react'; import EmptyPane from '@/components/empty-pane'; +import { FaCheckCircle } from 'react-icons/fa'; import Head from 'next/head'; import PageHeader from '@/components/page-header'; import { restApi } from '@/shared/services/api'; @@ -9,6 +10,7 @@ import { useQuery } from 'react-query'; import { Dataset, Product } from '@/shared/types/query.types'; import PreviewBanner from '@/components/preview-banner'; import DiscoveryList from '@/components/discovery-list'; +import useCurrentNamespace from '@/shared/hooks/use-current-namespace'; import { useAuth } from '@/shared/services/auth'; import NextLink from 'next/link'; @@ -20,9 +22,42 @@ const ApiDiscoveryPage: React.FC = () => { const { user } = useAuth(); const { data } = useQuery(['yourProducts', user?.namespace], () => restApi( - `/ds/api/v2/namespaces/${user?.namespace}/directory` + `/ds/api/v3/gateways/${user?.namespace}/directory` ) ); + const namespace = useCurrentNamespace(); + const hasNamespace = !!user?.namespace; + const title = ( + <> + {(namespace.isFetching || namespace.isLoading) && ( + + )} + {namespace.isSuccess && !namespace.isFetching && ( + <> + + {namespace.data?.currentNamespace?.displayName} + {namespace.data?.currentNamespace?.orgEnabled && ( + + + + + + )} + + + {namespace?.data.currentNamespace?.name} + + + )} + + ) return ( <> @@ -32,16 +67,14 @@ const ApiDiscoveryPage: React.FC = () => { - + {user && - 'A list of the published and unpublished products under your namespace'} + 'A list of the published and unpublished products under your gateway.'} {!user && 'You must be signed in to view this page'} - + {data?.length === 0 && ( { - const { user } = useAuth(); + const router = useRouter(); + router.push('/devportal/api-directory'); return ( <> API Program Services | Home - - - - - BC Government API Developer Portal - - - Discover and access APIs from various ministries and programs - across government. - - {/* {`What's New`} */} - - - - - - {actions - .filter( - (action) => - user?.roles.some((r: string) => action.roles.includes(r)) || - action.roles.length === 0 - ) - .map((action) => ( - - - - - - - {action.title} - - - -

- {action.description} -

-
-
- ))} -
-
); }; diff --git a/src/nextapp/pages/devportal/poc/access/[id].tsx b/src/nextapp/pages/devportal/poc/access/[id].tsx deleted file mode 100644 index b7ac4c830..000000000 --- a/src/nextapp/pages/devportal/poc/access/[id].tsx +++ /dev/null @@ -1,165 +0,0 @@ -import * as React from 'react'; -import { - Alert, - AlertTitle, - AlertDescription, - AlertIcon, - Button, - Box, - Container, - Divider, - Heading, - Icon, - Stack, - VStack, - Skeleton, - CloseButton, -} from '@chakra-ui/react'; -import Head from 'next/head'; -import PageHeader from '@/components/page-header'; - -import { useDisclosure } from '@chakra-ui/react'; - -import { GET_LIST, GET_REQUEST, GEN_CREDENTIAL } from './queries'; - -import { FaPlusCircle, FaFolder, FaFolderOpen } from 'react-icons/fa'; - -import { useAppContext } from '@/pages/context'; - -const { useEffect, useState } = React; - -import { styles } from '@/shared/styles/devportal.css'; - -import ReactMarkdownWithHtml from 'react-markdown/with-html'; -import gfm from 'remark-gfm'; - -import graphql from '@/shared/services/graphql'; - -import tmpstyles from '../../../docs/docs.module.css'; - -import List from './list'; - -import EnvironmentBadge from '@/components/environment-badge'; - -import ViewSecret from '@/components/view-secret'; - -const customStyles = { - content: { - top: '30%', - left: '20%', - right: '20%', - bottom: 'auto', - transformx: 'translate(-50%, -50%)', - }, - overlay: {}, -}; - -const MyApplicationsPage = () => { - const context = useAppContext(); - - const [request, setRequest] = useState(null); - - const [{ state, data }, setState] = useState({ - state: 'loading', - data: null, - }); - - const fetch = () => { - const { - router: { - pathname, - query: { id }, - }, - } = context; - - if (context['router'] != null && id) { - generateCredential(id) - .then(() => { - graphql(GET_LIST, {}).then(({ data }) => { - setState({ state: 'loaded', data }); - setRequest(data.allAccessRequests.filter((r) => r.id == id)[0]); - }); - }) - .catch((err) => { - setState({ state: 'error', data: null }); - }); - } - // if (context['router'] != null && id) { - - // graphql(GET_REQUEST, { id: id }).then(data => { - // setRequestDetails(data) - // }); - // } - }; - - const [{ cred, reqId }, setCred] = useState({ cred: null, reqId: null }); - - const onSecretClose = () => { - window.location.href = `/devportal/access`; - }; - - const generateCredential = (reqId) => { - return graphql(GEN_CREDENTIAL, { id: reqId }).then((data) => { - if (data.data.updateAccessRequest.credential != 'NEW') { - setCred({ - cred: JSON.parse(data.data.updateAccessRequest.credential), - reqId: reqId, - }); - } - }); - }; - - useEffect(fetch, [context]); - - const cancelRequest = (id) => {}; - - const { isOpen, onOpen, onClose } = useDisclosure(); - - const actions = []; - return ( - <> - - API Program Services | API Access - - - - - - List of the BC Government Service APIs that you have access to. - - - - - - - {request != null && cred != null && ( - - - - - {request.productEnvironment?.product.name} - - - - - -
- -
-
-
- )} - - {/* */} -
-
- - ); -}; - -export default MyApplicationsPage; diff --git a/src/nextapp/pages/devportal/poc/access/index.tsx b/src/nextapp/pages/devportal/poc/access/index.tsx deleted file mode 100644 index aa68a8efb..000000000 --- a/src/nextapp/pages/devportal/poc/access/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import * as React from 'react'; -import { - Alert, - AlertIcon, - Button, - Box, - Container, - Stack, - VStack, - Skeleton, - } from '@chakra-ui/react'; -import Head from 'next/head'; -import PageHeader from '@/components/page-header'; - -import { - useDisclosure - } from "@chakra-ui/react" - -import EmptyPane from '@/components/empty-pane'; - -import { GET_LIST, CANCEL_ACCESS } from './queries' - -//import { useAppContext } from '@/pages/context' - -const { useEffect, useState } = React - -import { styles } from '@/shared/styles/devportal.css' - -import graphql from '@/shared/services/graphql' - -import List from './list' - -const customStyles = { - content : { - top : '30%', - left : '20%', - right : '20%', - bottom : 'auto', - transformx : 'translate(-50%, -50%)' - }, - overlay: { - } -}; - -const MyApplicationsPage = () => { - - const [{ state, data}, setState] = useState({ state: 'loading', data: null }); - - const fetch = () => { - graphql(GET_LIST, {}) - .then(({ data }) => { - setState({ state: 'loaded', data }); - }) - .catch((err) => { - setState({ state: 'error', data: null }); - }); - }; - - useEffect(fetch, []); - - const cancelRequest = (id) => { - graphql(CANCEL_ACCESS, {id}) - .then(({ data }) => { - fetch() - }) - .catch((err) => { - console.log(err) - fetch() - }); - }; - - const { isOpen, onOpen, onClose } = useDisclosure() - - const actions = [ - ] - return ( - <> - - API Program Services | API Access - - - - - - List of the BC Government Service APIs that you have access to. - - - - - - - - - { data && data.allServiceAccesses.filter(s => s.productEnvironment != null).length == 0 && ( - - - - )} - - - - - - - ) -} - -export default MyApplicationsPage; diff --git a/src/nextapp/pages/devportal/poc/access/list.tsx b/src/nextapp/pages/devportal/poc/access/list.tsx deleted file mode 100644 index 0e0e450dd..000000000 --- a/src/nextapp/pages/devportal/poc/access/list.tsx +++ /dev/null @@ -1,347 +0,0 @@ -import * as React from 'react'; - -import { styles } from '@/shared/styles/devportal.css'; - -import graphql from '@/shared/services/graphql'; - -import { GEN_CREDENTIAL } from './queries'; - -import { - Alert, - AlertDescription, - AlertTitle, - AlertIcon, - Box, - Badge, - Icon, - CloseButton, - Divider, - Heading, - Input, - InputGroup, - InputRightElement, - Link, - Stack, - Tag, - TagLabel, - Button, - ButtonGroup, - Text, - Spacer, -} from '@chakra-ui/react'; -import { - HStack, - Table, - Thead, - Tbody, - Tr, - Th, - Td, - TableCaption, - VStack, -} from '@chakra-ui/react'; - -import { Tabs, TabList, TabPanels, Tab, TabPanel } from '@chakra-ui/react'; - -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { dark } from 'react-syntax-highlighter/dist/esm/styles/prism'; - -import { FaPlusCircle, FaFolder, FaFolderOpen } from 'react-icons/fa'; - -import ReactMarkdownWithHtml from 'react-markdown/with-html'; -import gfm from 'remark-gfm'; - -import EnvironmentBadge from '@/components/environment-badge'; - -import NameValue from '@/components/name-value'; - -import tmpstyles from '../../docs/docs.module.css'; - -const { useEffect, useState } = React; - -const TEMPL_API_KEY_CURL = ` -# Use the API Key that was generated -# for you when requesting access. -curl -v /status -H "X-API-KEY: " -`; - -const TEMPL_CLIENT_CURL = ` -# Use the Client Creds that was generated -# for you when requesting access. -export TOKEN_ENDPOINT="" -export CLIENT_ID="" -export CLIENT_SECRET="" - -curl -X POST $TOKEN_ENDPOINT \\ - -d grant_type=client_credentials \\ - -d client_id=$CLIENT_ID \\ - -d client_secret=$CLIENT_SECRET \\ - -d scope=openid - -# Extract the token -export TOK="" - -curl -v /status -H "Authorization: Bearer $TOK" - -`; - -const TEMPL_API_KEY_RESTISH = ` -# Use the API Key that was generated -# for you when requesting access. - -restish api config myapi - -? Base URI : - -> Add Header - -Header name: X-API-KEY -Header value: - -> Finished with Profile -> Save and exit - -restish get myapi/ -`; - -const TEMPL_CLIENT_RESTISH = ` -restish api config myapi - -? Base URI : - -> Setup auth - -Select 'oauth-client-credentials' - -Auth parameter client_id: -Auth parameter client_secret: -Auth parameter token_url: -Auth parameter scopes: openid -Add additional auth param? N - -> Finished with profile -> Save and exit - -restish get myapi/ -`; - -const TEMPLATES = { - 'kong-acl-only': [TEMPL_API_KEY_CURL, TEMPL_API_KEY_RESTISH], - 'kong-api-key-acl': [TEMPL_API_KEY_CURL, TEMPL_API_KEY_RESTISH], - 'client-credentials': [TEMPL_CLIENT_CURL, TEMPL_CLIENT_RESTISH], -}; -function List({ data, state, refetch, cancelRequest }) { - switch (state) { - case 'loading': { - return

Loading...

; - } - case 'error': { - return

Error!

; - } - case 'loaded': { - if (!data) { - return

Ooops, something went wrong!

; - } - const products = [ - ...new Set( - data.myServiceAccesses - .map((item, index) => item.productEnvironment?.product.name) - .filter((p) => p != null) - ), - ]; - - return ( - <> - {products.sort().map((product) => ( - - - - - {product} - - - - - - - - - - - - - - {data.myServiceAccesses - .filter( - (access) => - access.productEnvironment?.product.name == product - ) - .map((item, index) => { - const [show, setShow] = React.useState(false); - - return ( - <> - - - - - - {item.active == true && show ? ( - - - - ) : ( - false - )} - - ); - })} - -
EnvironmentEndpoints
- - - {item.productEnvironment.name} - - - - {item.productEnvironment.services.map((svc) => { - return svc.routes.map((route) => { - const _methods = JSON.parse(route['methods']); - const methods = - Array.isArray(_methods) && - _methods.length > 0 - ? _methods - : ['ALL']; - const hosts = Array.isArray( - JSON.parse(route['hosts']) - ) - ? JSON.parse(route['hosts']) - : []; - const paths = Array.isArray( - JSON.parse(route['paths']) - ) - ? JSON.parse(route['paths']) - : ['/']; - const hostPaths = hosts.map((h) => - paths.map((p) => `https://${h}${p}`) - ); - return ( - - {hostPaths.map((hp) => ( - <> - - {methods.map((p) => ( - - {p} - - ))} - - {hp} - - ))} - - ); - }); - })} - - - {item.active == false && ( - - PENDING APPROVAL - - )} - - - {item.active == true && ( - - )} - -
- - - Curl - Restish - Swagger Console - Postman - - - - - - { - TEMPLATES[ - item.productEnvironment.flow - ][0] - } - - - - - { - TEMPLATES[ - item.productEnvironment.flow - ][1] - } - - - -

Coming soon!

-
- -

Coming soon!

-
-
-
-
-
- ))} - - ); - } - } - return <>; -} - -export default List; diff --git a/src/nextapp/pages/devportal/poc/access/queries.js b/src/nextapp/pages/devportal/poc/access/queries.js deleted file mode 100644 index 409a0d3e1..000000000 --- a/src/nextapp/pages/devportal/poc/access/queries.js +++ /dev/null @@ -1,120 +0,0 @@ -export const GET_LIST = ` - query GET { - allTemporaryIdentities { - id - userId - } - myServiceAccesses(where: { }) { - id - name - active - application { - appId - } - productEnvironment { - name - flow - credentialIssuer { - instruction - } - product { - name - } - services { - name - routes { - name - hosts - methods - paths - } - } - } - } - allAccessRequests(where: { isComplete: null }) { - id - name - isIssued - application { - appId - } - productEnvironment { - name - flow - credentialIssuer { - instruction - } - product { - name - } - services { - name - routes { - name - hosts - methods - paths - } - } - } - } - } -` - -export const GET_REQUEST = ` - query GetRequestById($requestId: ID!) { - allAccessRequests(where: { id: $requestId }) { - id - name - isIssued - application { - appId - } - productEnvironment { - name - credentialIssuer { - instruction - } - product { - name - } - services { - name - routes { - name - hosts - methods - paths - } - } - } - } - } -` -// export const GEN_CREDENTIAL = ` -// mutation GenCredential($id: ID!) { -// updateServiceAccess(id: $id, data: { credential: "NEW" }) { -// credential -// } -// } -// ` - -export const GEN_CREDENTIAL = ` - mutation GenCredential($id: ID!) { - updateAccessRequest(id: $id, data: { credential: "NEW" }) { - credential - } - } -` - -export const CANCEL_ACCESS = ` - mutation CancelAccess($id: ID!) { - deleteServiceAccess(id: $id) { - id - } - } -` - - -const empty = () => false -export default empty diff --git a/src/nextapp/pages/devportal/poc/requests/new/[id].tsx b/src/nextapp/pages/devportal/poc/requests/new/[id].tsx deleted file mode 100644 index c3b025cdd..000000000 --- a/src/nextapp/pages/devportal/poc/requests/new/[id].tsx +++ /dev/null @@ -1,160 +0,0 @@ -import * as React from 'react'; -import { - Alert, - AlertIcon, - Box, - Container, - VStack, - Skeleton, - } from '@chakra-ui/react'; -import Head from 'next/head'; -import PageHeader from '@/components/page-header'; - -import { useRouter } from 'next/router' - -const { useEffect, useState } = React; - -import { ADD, GET_PRODUCT } from './../queries' - -import { styles } from '@/shared/styles/devportal.css'; - -import graphql from '@/shared/services/graphql' - -import NameValue from '@/components/name-value'; - -import { Button, ButtonGroup, Link, Textarea, RadioGroup, Radio, Stack, Flex } from "@chakra-ui/react" - -import { useAppContext } from '@/pages/context' - -import { create } from 'domain'; - -const NewRequest = () => { - const context = useAppContext() - - const [environmentId, setEnvironmentId] = useState(); - const [applicationId, setApplicationId] = useState(); - - const [{ state, data }, setState] = useState({ state: 'loading', data: null }); - const fetch = () => { - const { router: { pathname, query: { id } } } = context - if (context['router'] != null && id) { - graphql(GET_PRODUCT, { id : id }) - .then(({ data }) => { - setState({ state: 'loaded', data }); - }) - .catch((err) => { - setState({ state: 'error', data: null }); - }); - } - }; - useEffect(fetch, [context]); - - const requestor = (data ? data.allTemporaryIdentities[0] : null) - const dataset = (data ? data.allProducts[0] : data) - - const refetch = (data) => { - window.location.href = `/devportal/poc/access/${data.data.createAccessRequest.id}` - } - - - const create = () => { - graphql(ADD, { name: dataset.name + " FOR " + data.allTemporaryIdentities[0].name, controls: "{}", requestor: data.allTemporaryIdentities[0].userId, applicationId: applicationId, productEnvironmentId: environmentId }).then(refetch); - } - //onChange={(a) => { setApplicationId(a) } } value={applicationId} - //onChange={setEnvironmentId} value={environmentId} - return ( - <> - - API Program Services | Request Access - - - - - - - -
- - { dataset == null ? false: ( - <> - { data.allApplications == null || data.allApplications.length == 0 ? ( - To get started, you must Register an Application first. - ) : false } - { data.allApplications != null && data.allApplications.length > 0 && ( - <> -

APIs

-
- - -
{dataset.name}
-
-
- -

Which {dataset.name} Environment?

- { dataset.environments != null ? ( -
- - - - - { dataset.environments.filter(e => e.active).map(e => ( - {e.name} : {e.flow} - ))} - - - -
- ): false} - -

This API is Protected with the OAuth Flow, so requesting as yourself.

-
- - - -
-

OR Select which application will be using this API?

- - - - { data.allApplications == null || data.allApplications.length == 0 ? ( - To get started, you must Register an Application first. - ) : ( - <> - - - - { data.allApplications.map(e => ( - {e.name} - ))} - - - - - ) } - - -

All terms and conditions agreed?

- -

Additional Instruction

-
-