diff --git a/.buildkite/scripts/steps/capture_oas_snapshot.sh b/.buildkite/scripts/steps/capture_oas_snapshot.sh index 79dfdd96c8fc6..3c82fca5013c5 100755 --- a/.buildkite/scripts/steps/capture_oas_snapshot.sh +++ b/.buildkite/scripts/steps/capture_oas_snapshot.sh @@ -5,7 +5,7 @@ set -euo pipefail source .buildkite/scripts/common/util.sh echo --- Capture OAS snapshot -cmd="node scripts/capture_oas_snapshot --include-path /api/status --include-path /api/alerting/rule/ --include-path /api/alerting/rules --include-path /api/actions" +cmd="node scripts/capture_oas_snapshot --include-path /api/status --include-path /api/alerting/rule/ --include-path /api/alerting/rules --include-path /api/actions --include-path /api/security/role --include-path /api/spaces" if is_pr && ! is_auto_commit_disabled; then cmd="$cmd --update" fi diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index 5fd690a2f6ceb..7c27f050640ec 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -6263,6 +6263,1427 @@ ] } }, + "/api/security/role": { + "get": { + "operationId": "%2Fapi%2Fsecurity%2Frole#0", + "parameters": [], + "responses": {}, + "summary": "Get all roles", + "tags": [ + "roles" + ] + } + }, + "/api/security/role/{name}": { + "delete": { + "operationId": "%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#1", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "required": true, + "schema": { + "minLength": 1, + "type": "string" + } + } + ], + "responses": {}, + "summary": "Delete a role", + "tags": [ + "roles" + ] + }, + "get": { + "operationId": "%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "required": true, + "schema": { + "minLength": 1, + "type": "string" + } + } + ], + "responses": {}, + "summary": "Get a role", + "tags": [ + "roles" + ] + }, + "put": { + "operationId": "%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#2", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "name", + "required": true, + "schema": { + "maxLength": 1024, + "minLength": 1, + "type": "string" + } + }, + { + "in": "query", + "name": "createOnly", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "description": { + "maxLength": 2048, + "type": "string" + }, + "elasticsearch": { + "additionalProperties": false, + "properties": { + "cluster": { + "items": { + "type": "string" + }, + "type": "array" + }, + "indices": { + "items": { + "additionalProperties": false, + "properties": { + "allow_restricted_indices": { + "type": "boolean" + }, + "field_security": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "names": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "query": { + "type": "string" + } + }, + "required": [ + "names", + "privileges" + ], + "type": "object" + }, + "type": "array" + }, + "remote_cluster": { + "items": { + "additionalProperties": false, + "properties": { + "clusters": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "privileges", + "clusters" + ], + "type": "object" + }, + "type": "array" + }, + "remote_indices": { + "items": { + "additionalProperties": false, + "properties": { + "allow_restricted_indices": { + "type": "boolean" + }, + "clusters": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "field_security": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "names": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "query": { + "type": "string" + } + }, + "required": [ + "clusters", + "names", + "privileges" + ], + "type": "object" + }, + "type": "array" + }, + "run_as": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "kibana": { + "items": { + "additionalProperties": false, + "properties": { + "base": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "object" + }, + { + "type": "string" + } + ], + "nullable": true, + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "feature": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "spaces": { + "anyOf": [ + { + "items": { + "enum": [ + "*" + ], + "type": "string" + }, + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": [ + "*" + ] + } + }, + "required": [ + "base" + ], + "type": "object" + }, + "type": "array" + }, + "metadata": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "elasticsearch" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "Create or update a role", + "tags": [ + "roles" + ] + } + }, + "/api/security/roles": { + "post": { + "operationId": "%2Fapi%2Fsecurity%2Froles#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "roles": { + "additionalProperties": { + "additionalProperties": false, + "properties": { + "description": { + "maxLength": 2048, + "type": "string" + }, + "elasticsearch": { + "additionalProperties": false, + "properties": { + "cluster": { + "items": { + "type": "string" + }, + "type": "array" + }, + "indices": { + "items": { + "additionalProperties": false, + "properties": { + "allow_restricted_indices": { + "type": "boolean" + }, + "field_security": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "names": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "query": { + "type": "string" + } + }, + "required": [ + "names", + "privileges" + ], + "type": "object" + }, + "type": "array" + }, + "remote_cluster": { + "items": { + "additionalProperties": false, + "properties": { + "clusters": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "privileges", + "clusters" + ], + "type": "object" + }, + "type": "array" + }, + "remote_indices": { + "items": { + "additionalProperties": false, + "properties": { + "allow_restricted_indices": { + "type": "boolean" + }, + "clusters": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "field_security": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "names": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "privileges": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + }, + "query": { + "type": "string" + } + }, + "required": [ + "clusters", + "names", + "privileges" + ], + "type": "object" + }, + "type": "array" + }, + "run_as": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "kibana": { + "items": { + "additionalProperties": false, + "properties": { + "base": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "object" + }, + { + "type": "string" + } + ], + "nullable": true, + "oneOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "feature": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "spaces": { + "anyOf": [ + { + "items": { + "enum": [ + "*" + ], + "type": "string" + }, + "maxItems": 1, + "minItems": 1, + "type": "array" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ], + "default": [ + "*" + ] + } + }, + "required": [ + "base" + ], + "type": "object" + }, + "type": "array" + }, + "metadata": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "elasticsearch" + ], + "type": "object" + }, + "type": "object" + } + }, + "required": [ + "roles" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "Create or update roles", + "tags": [ + "roles" + ] + } + }, + "/api/spaces/_copy_saved_objects": { + "post": { + "description": "Copy saved objects to spaces", + "operationId": "%2Fapi%2Fspaces%2F_copy_saved_objects#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "compatibilityMode": { + "default": false, + "type": "boolean" + }, + "createNewCopies": { + "default": true, + "type": "boolean" + }, + "includeReferences": { + "default": false, + "type": "boolean" + }, + "objects": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "id" + ], + "type": "object" + }, + "type": "array" + }, + "overwrite": { + "default": false, + "type": "boolean" + }, + "spaces": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "spaces", + "objects" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "", + "tags": [] + } + }, + "/api/spaces/_disable_legacy_url_aliases": { + "post": { + "description": "Disable legacy URL aliases", + "operationId": "%2Fapi%2Fspaces%2F_disable_legacy_url_aliases#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "aliases": { + "items": { + "additionalProperties": false, + "properties": { + "sourceId": { + "type": "string" + }, + "targetSpace": { + "type": "string" + }, + "targetType": { + "type": "string" + } + }, + "required": [ + "targetSpace", + "targetType", + "sourceId" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "aliases" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "", + "tags": [] + } + }, + "/api/spaces/_get_shareable_references": { + "post": { + "description": "Get shareable references", + "operationId": "%2Fapi%2Fspaces%2F_get_shareable_references#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "objects": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "id" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "objects" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "", + "tags": [] + } + }, + "/api/spaces/_resolve_copy_saved_objects_errors": { + "post": { + "description": "Resolve conflicts copying saved objects", + "operationId": "%2Fapi%2Fspaces%2F_resolve_copy_saved_objects_errors#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "compatibilityMode": { + "default": false, + "type": "boolean" + }, + "createNewCopies": { + "default": true, + "type": "boolean" + }, + "includeReferences": { + "default": false, + "type": "boolean" + }, + "objects": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "id" + ], + "type": "object" + }, + "type": "array" + }, + "retries": { + "additionalProperties": { + "items": { + "additionalProperties": false, + "properties": { + "createNewCopy": { + "type": "boolean" + }, + "destinationId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "ignoreMissingReferences": { + "type": "boolean" + }, + "overwrite": { + "default": false, + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "id" + ], + "type": "object" + }, + "type": "array" + }, + "type": "object" + } + }, + "required": [ + "retries", + "objects" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "", + "tags": [] + } + }, + "/api/spaces/_update_objects_spaces": { + "post": { + "description": "Update saved objects in spaces", + "operationId": "%2Fapi%2Fspaces%2F_update_objects_spaces#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "objects": { + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "id" + ], + "type": "object" + }, + "type": "array" + }, + "spacesToAdd": { + "items": { + "type": "string" + }, + "type": "array" + }, + "spacesToRemove": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "objects", + "spacesToAdd", + "spacesToRemove" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "", + "tags": [] + } + }, + "/api/spaces/space": { + "get": { + "description": "Get all spaces", + "operationId": "%2Fapi%2Fspaces%2Fspace#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "in": "query", + "name": "purpose", + "required": false, + "schema": { + "enum": [ + "any", + "copySavedObjectsIntoSpace", + "shareSavedObjectsIntoSpace" + ], + "type": "string" + } + }, + { + "in": "query", + "name": "include_authorized_purposes", + "required": true, + "schema": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "object" + }, + { + "type": "string" + } + ], + "nullable": true, + "oneOf": [ + { + "enum": [ + false + ], + "type": "boolean", + "x-oas-optional": true + }, + { + "type": "boolean", + "x-oas-optional": true + } + ] + } + } + ], + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + }, + "post": { + "description": "Create a space", + "operationId": "%2Fapi%2Fspaces%2Fspace#1", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string" + }, + "disabledFeatures": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "initials": { + "maxLength": 2, + "type": "string" + }, + "name": { + "minLength": 1, + "type": "string" + }, + "solution": { + "enum": [ + "security", + "oblt", + "es", + "classic" + ], + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + } + }, + "/api/spaces/space/{id}": { + "delete": { + "description": "Delete a space", + "operationId": "%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#2", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + }, + "get": { + "description": "Get a space", + "operationId": "%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + }, + "put": { + "description": "Update a space", + "operationId": "%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#1", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string" + }, + "disabledFeatures": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "initials": { + "maxLength": 2, + "type": "string" + }, + "name": { + "minLength": 1, + "type": "string" + }, + "solution": { + "enum": [ + "security", + "oblt", + "es", + "classic" + ], + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + } + }, "/api/status": { "get": { "operationId": "%2Fapi%2Fstatus#0", @@ -6360,6 +7781,12 @@ { "name": "connectors" }, + { + "name": "roles" + }, + { + "name": "spaces" + }, { "name": "system" } diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index 5fd690a2f6ceb..c58eef641d6d7 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -6263,6 +6263,323 @@ ] } }, + "/api/spaces/space": { + "get": { + "description": "Get all spaces", + "operationId": "%2Fapi%2Fspaces%2Fspace#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "in": "query", + "name": "purpose", + "required": false, + "schema": { + "enum": [ + "any", + "copySavedObjectsIntoSpace", + "shareSavedObjectsIntoSpace" + ], + "type": "string" + } + }, + { + "in": "query", + "name": "include_authorized_purposes", + "required": true, + "schema": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "object" + }, + { + "type": "string" + } + ], + "nullable": true, + "oneOf": [ + { + "enum": [ + false + ], + "type": "boolean", + "x-oas-optional": true + }, + { + "type": "boolean", + "x-oas-optional": true + } + ] + } + } + ], + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + }, + "post": { + "description": "Create a space", + "operationId": "%2Fapi%2Fspaces%2Fspace#1", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string" + }, + "disabledFeatures": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "initials": { + "maxLength": 2, + "type": "string" + }, + "name": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + } + }, + "/api/spaces/space/{id}": { + "delete": { + "description": "Delete a space", + "operationId": "%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#2", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + }, + "get": { + "description": "Get a space", + "operationId": "%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#0", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + }, + "put": { + "description": "Update a space", + "operationId": "%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#1", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string" + }, + "disabledFeatures": { + "default": [], + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "imageUrl": { + "type": "string" + }, + "initials": { + "maxLength": 2, + "type": "string" + }, + "name": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + } + } + } + }, + "responses": {}, + "summary": "", + "tags": [ + "spaces" + ] + } + }, "/api/status": { "get": { "operationId": "%2Fapi%2Fstatus#0", @@ -6360,6 +6677,9 @@ { "name": "connectors" }, + { + "name": "spaces" + }, { "name": "system" } diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index 17de64aabc4ef..39819e8307e75 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -16241,6 +16241,214 @@ paths: tags: - Security AI Assistant API - Prompts API + /api/spaces/space: + get: + description: Get all spaces + operationId: '%2Fapi%2Fspaces%2Fspace#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: query + name: purpose + required: false + schema: + enum: + - any + - copySavedObjectsIntoSpace + - shareSavedObjectsIntoSpace + type: string + - in: query + name: include_authorized_purposes + required: true + schema: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - enum: + - false + type: boolean + x-oas-optional: true + - type: boolean + x-oas-optional: true + responses: {} + summary: '' + tags: + - spaces + post: + description: Create a space + operationId: '%2Fapi%2Fspaces%2Fspace#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _reserved: + type: boolean + color: + type: string + description: + type: string + disabledFeatures: + default: [] + items: + type: string + type: array + id: + type: string + imageUrl: + type: string + initials: + maxLength: 2 + type: string + name: + minLength: 1 + type: string + required: + - id + - name + responses: {} + summary: '' + tags: + - spaces + /api/spaces/space/{id}: + delete: + description: Delete a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#2' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: id + required: true + schema: + type: string + responses: {} + summary: '' + tags: + - spaces + get: + description: Get a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: path + name: id + required: true + schema: + type: string + responses: {} + summary: '' + tags: + - spaces + put: + description: Update a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _reserved: + type: boolean + color: + type: string + description: + type: string + disabledFeatures: + default: [] + items: + type: string + type: array + id: + type: string + imageUrl: + type: string + initials: + maxLength: 2 + type: string + name: + minLength: 1 + type: string + required: + - id + - name + responses: {} + summary: '' + tags: + - spaces /api/status: get: operationId: '%2Fapi%2Fstatus#0' @@ -33323,4 +33531,5 @@ tags: name: Security Timeline API - description: SLO APIs enable you to define, manage and track service-level objectives name: slo + - name: spaces - name: system diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 1b4209e9eec0a..c30f683b3bf97 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -9860,6 +9860,214 @@ paths: -X POST api/saved_objects/_import?createNewCopies=true -H "kbn-xsrf: true" --form file=@file.ndjson + /api/spaces/space: + get: + description: Get all spaces + operationId: '%2Fapi%2Fspaces%2Fspace#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: query + name: purpose + required: false + schema: + enum: + - any + - copySavedObjectsIntoSpace + - shareSavedObjectsIntoSpace + type: string + - in: query + name: include_authorized_purposes + required: true + schema: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - enum: + - false + type: boolean + x-oas-optional: true + - type: boolean + x-oas-optional: true + responses: {} + summary: '' + tags: + - spaces + post: + description: Create a space + operationId: '%2Fapi%2Fspaces%2Fspace#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _reserved: + type: boolean + color: + type: string + description: + type: string + disabledFeatures: + default: [] + items: + type: string + type: array + id: + type: string + imageUrl: + type: string + initials: + maxLength: 2 + type: string + name: + minLength: 1 + type: string + required: + - id + - name + responses: {} + summary: '' + tags: + - spaces + /api/spaces/space/{id}: + delete: + description: Delete a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#2' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: id + required: true + schema: + type: string + responses: {} + summary: '' + tags: + - spaces + get: + description: Get a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: path + name: id + required: true + schema: + type: string + responses: {} + summary: '' + tags: + - spaces + put: + description: Update a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _reserved: + type: boolean + color: + type: string + description: + type: string + disabledFeatures: + default: [] + items: + type: string + type: array + id: + type: string + imageUrl: + type: string + initials: + maxLength: 2 + type: string + name: + minLength: 1 + type: string + required: + - id + - name + responses: {} + summary: '' + tags: + - spaces /api/status: get: operationId: '%2Fapi%2Fstatus#0' @@ -16476,4 +16684,5 @@ tags: x-displayName: Saved objects - description: SLO APIs enable you to define, manage and track service-level objectives name: slo + - name: spaces - name: system diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml index 5830ef5ce40f7..3114d0bb7622a 100644 --- a/oas_docs/output/kibana.staging.yaml +++ b/oas_docs/output/kibana.staging.yaml @@ -20330,6 +20330,949 @@ paths: tags: - Security AI Assistant API - Prompts API + /api/security/role: + get: + operationId: '%2Fapi%2Fsecurity%2Frole#0' + parameters: [] + responses: {} + summary: Get all roles + tags: + - roles + /api/security/role/{name}: + delete: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: name + required: true + schema: + minLength: 1 + type: string + responses: {} + summary: Delete a role + tags: + - roles + get: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: path + name: name + required: true + schema: + minLength: 1 + type: string + responses: {} + summary: Get a role + tags: + - roles + put: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#2' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: name + required: true + schema: + maxLength: 1024 + minLength: 1 + type: string + - in: query + name: createOnly + required: false + schema: + default: false + type: boolean + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + description: + maxLength: 2048 + type: string + elasticsearch: + additionalProperties: false + type: object + properties: + cluster: + items: + type: string + type: array + indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - names + - privileges + type: array + remote_cluster: + items: + additionalProperties: false + type: object + properties: + clusters: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + required: + - privileges + - clusters + type: array + remote_indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + clusters: + items: + type: string + minItems: 1 + type: array + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - clusters + - names + - privileges + type: array + run_as: + items: + type: string + type: array + kibana: + items: + additionalProperties: false + type: object + properties: + base: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - items: + type: string + type: array + - items: + type: string + type: array + feature: + additionalProperties: + items: + type: string + type: array + type: object + spaces: + anyOf: + - items: + enum: + - '*' + type: string + maxItems: 1 + minItems: 1 + type: array + - items: + type: string + type: array + default: + - '*' + required: + - base + type: array + metadata: + additionalProperties: {} + type: object + required: + - elasticsearch + responses: {} + summary: Create or update a role + tags: + - roles + /api/security/roles: + post: + operationId: '%2Fapi%2Fsecurity%2Froles#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + roles: + additionalProperties: + additionalProperties: false + type: object + properties: + description: + maxLength: 2048 + type: string + elasticsearch: + additionalProperties: false + type: object + properties: + cluster: + items: + type: string + type: array + indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - names + - privileges + type: array + remote_cluster: + items: + additionalProperties: false + type: object + properties: + clusters: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + required: + - privileges + - clusters + type: array + remote_indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + clusters: + items: + type: string + minItems: 1 + type: array + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - clusters + - names + - privileges + type: array + run_as: + items: + type: string + type: array + kibana: + items: + additionalProperties: false + type: object + properties: + base: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - items: + type: string + type: array + - items: + type: string + type: array + feature: + additionalProperties: + items: + type: string + type: array + type: object + spaces: + anyOf: + - items: + enum: + - '*' + type: string + maxItems: 1 + minItems: 1 + type: array + - items: + type: string + type: array + default: + - '*' + required: + - base + type: array + metadata: + additionalProperties: {} + type: object + required: + - elasticsearch + type: object + required: + - roles + responses: {} + summary: Create or update roles + tags: + - roles + /api/spaces/_copy_saved_objects: + post: + description: Copy saved objects to spaces + operationId: '%2Fapi%2Fspaces%2F_copy_saved_objects#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + compatibilityMode: + default: false + type: boolean + createNewCopies: + default: true + type: boolean + includeReferences: + default: false + type: boolean + objects: + items: + additionalProperties: false + type: object + properties: + id: + type: string + type: + type: string + required: + - type + - id + type: array + overwrite: + default: false + type: boolean + spaces: + items: + type: string + type: array + required: + - spaces + - objects + responses: {} + summary: '' + tags: [] + /api/spaces/_disable_legacy_url_aliases: + post: + description: Disable legacy URL aliases + operationId: '%2Fapi%2Fspaces%2F_disable_legacy_url_aliases#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + aliases: + items: + additionalProperties: false + type: object + properties: + sourceId: + type: string + targetSpace: + type: string + targetType: + type: string + required: + - targetSpace + - targetType + - sourceId + type: array + required: + - aliases + responses: {} + summary: '' + tags: [] + /api/spaces/_get_shareable_references: + post: + description: Get shareable references + operationId: '%2Fapi%2Fspaces%2F_get_shareable_references#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + objects: + items: + additionalProperties: false + type: object + properties: + id: + type: string + type: + type: string + required: + - type + - id + type: array + required: + - objects + responses: {} + summary: '' + tags: [] + /api/spaces/_resolve_copy_saved_objects_errors: + post: + description: Resolve conflicts copying saved objects + operationId: '%2Fapi%2Fspaces%2F_resolve_copy_saved_objects_errors#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + compatibilityMode: + default: false + type: boolean + createNewCopies: + default: true + type: boolean + includeReferences: + default: false + type: boolean + objects: + items: + additionalProperties: false + type: object + properties: + id: + type: string + type: + type: string + required: + - type + - id + type: array + retries: + additionalProperties: + items: + additionalProperties: false + type: object + properties: + createNewCopy: + type: boolean + destinationId: + type: string + id: + type: string + ignoreMissingReferences: + type: boolean + overwrite: + default: false + type: boolean + type: + type: string + required: + - type + - id + type: array + type: object + required: + - retries + - objects + responses: {} + summary: '' + tags: [] + /api/spaces/_update_objects_spaces: + post: + description: Update saved objects in spaces + operationId: '%2Fapi%2Fspaces%2F_update_objects_spaces#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + objects: + items: + additionalProperties: false + type: object + properties: + id: + type: string + type: + type: string + required: + - type + - id + type: array + spacesToAdd: + items: + type: string + type: array + spacesToRemove: + items: + type: string + type: array + required: + - objects + - spacesToAdd + - spacesToRemove + responses: {} + summary: '' + tags: [] + /api/spaces/space: + get: + description: Get all spaces + operationId: '%2Fapi%2Fspaces%2Fspace#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: query + name: purpose + required: false + schema: + enum: + - any + - copySavedObjectsIntoSpace + - shareSavedObjectsIntoSpace + type: string + - in: query + name: include_authorized_purposes + required: true + schema: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - enum: + - false + type: boolean + x-oas-optional: true + - type: boolean + x-oas-optional: true + responses: {} + summary: '' + tags: + - spaces + post: + description: Create a space + operationId: '%2Fapi%2Fspaces%2Fspace#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _reserved: + type: boolean + color: + type: string + description: + type: string + disabledFeatures: + default: [] + items: + type: string + type: array + id: + type: string + imageUrl: + type: string + initials: + maxLength: 2 + type: string + name: + minLength: 1 + type: string + solution: + enum: + - security + - oblt + - es + - classic + type: string + required: + - id + - name + responses: {} + summary: '' + tags: + - spaces + /api/spaces/space/{id}: + delete: + description: Delete a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#2' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: id + required: true + schema: + type: string + responses: {} + summary: '' + tags: + - spaces + get: + description: Get a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: path + name: id + required: true + schema: + type: string + responses: {} + summary: '' + tags: + - spaces + put: + description: Update a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _reserved: + type: boolean + color: + type: string + description: + type: string + disabledFeatures: + default: [] + items: + type: string + type: array + id: + type: string + imageUrl: + type: string + initials: + maxLength: 2 + type: string + name: + minLength: 1 + type: string + solution: + enum: + - security + - oblt + - es + - classic + type: string + required: + - id + - name + responses: {} + summary: '' + tags: + - spaces /api/status: get: operationId: '%2Fapi%2Fstatus#0' @@ -41270,6 +42213,7 @@ tags: - name: Fleet uninstall tokens - description: Machine learning name: ml + - name: roles - description: > Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects @@ -41325,4 +42269,5 @@ tags: name: Security Timeline API - description: SLO APIs enable you to define, manage and track service-level objectives name: slo + - name: spaces - name: system diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 35a446f538a6a..da28a9a3ade65 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -13132,6 +13132,949 @@ paths: summary: Resolve a saved object tags: - saved objects + /api/security/role: + get: + operationId: '%2Fapi%2Fsecurity%2Frole#0' + parameters: [] + responses: {} + summary: Get all roles + tags: + - roles + /api/security/role/{name}: + delete: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: name + required: true + schema: + minLength: 1 + type: string + responses: {} + summary: Delete a role + tags: + - roles + get: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: path + name: name + required: true + schema: + minLength: 1 + type: string + responses: {} + summary: Get a role + tags: + - roles + put: + operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#2' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: name + required: true + schema: + maxLength: 1024 + minLength: 1 + type: string + - in: query + name: createOnly + required: false + schema: + default: false + type: boolean + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + description: + maxLength: 2048 + type: string + elasticsearch: + additionalProperties: false + type: object + properties: + cluster: + items: + type: string + type: array + indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - names + - privileges + type: array + remote_cluster: + items: + additionalProperties: false + type: object + properties: + clusters: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + required: + - privileges + - clusters + type: array + remote_indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + clusters: + items: + type: string + minItems: 1 + type: array + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - clusters + - names + - privileges + type: array + run_as: + items: + type: string + type: array + kibana: + items: + additionalProperties: false + type: object + properties: + base: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - items: + type: string + type: array + - items: + type: string + type: array + feature: + additionalProperties: + items: + type: string + type: array + type: object + spaces: + anyOf: + - items: + enum: + - '*' + type: string + maxItems: 1 + minItems: 1 + type: array + - items: + type: string + type: array + default: + - '*' + required: + - base + type: array + metadata: + additionalProperties: {} + type: object + required: + - elasticsearch + responses: {} + summary: Create or update a role + tags: + - roles + /api/security/roles: + post: + operationId: '%2Fapi%2Fsecurity%2Froles#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + roles: + additionalProperties: + additionalProperties: false + type: object + properties: + description: + maxLength: 2048 + type: string + elasticsearch: + additionalProperties: false + type: object + properties: + cluster: + items: + type: string + type: array + indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - names + - privileges + type: array + remote_cluster: + items: + additionalProperties: false + type: object + properties: + clusters: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + required: + - privileges + - clusters + type: array + remote_indices: + items: + additionalProperties: false + type: object + properties: + allow_restricted_indices: + type: boolean + clusters: + items: + type: string + minItems: 1 + type: array + field_security: + additionalProperties: + items: + type: string + type: array + type: object + names: + items: + type: string + minItems: 1 + type: array + privileges: + items: + type: string + minItems: 1 + type: array + query: + type: string + required: + - clusters + - names + - privileges + type: array + run_as: + items: + type: string + type: array + kibana: + items: + additionalProperties: false + type: object + properties: + base: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - items: + type: string + type: array + - items: + type: string + type: array + feature: + additionalProperties: + items: + type: string + type: array + type: object + spaces: + anyOf: + - items: + enum: + - '*' + type: string + maxItems: 1 + minItems: 1 + type: array + - items: + type: string + type: array + default: + - '*' + required: + - base + type: array + metadata: + additionalProperties: {} + type: object + required: + - elasticsearch + type: object + required: + - roles + responses: {} + summary: Create or update roles + tags: + - roles + /api/spaces/_copy_saved_objects: + post: + description: Copy saved objects to spaces + operationId: '%2Fapi%2Fspaces%2F_copy_saved_objects#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + compatibilityMode: + default: false + type: boolean + createNewCopies: + default: true + type: boolean + includeReferences: + default: false + type: boolean + objects: + items: + additionalProperties: false + type: object + properties: + id: + type: string + type: + type: string + required: + - type + - id + type: array + overwrite: + default: false + type: boolean + spaces: + items: + type: string + type: array + required: + - spaces + - objects + responses: {} + summary: '' + tags: [] + /api/spaces/_disable_legacy_url_aliases: + post: + description: Disable legacy URL aliases + operationId: '%2Fapi%2Fspaces%2F_disable_legacy_url_aliases#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + aliases: + items: + additionalProperties: false + type: object + properties: + sourceId: + type: string + targetSpace: + type: string + targetType: + type: string + required: + - targetSpace + - targetType + - sourceId + type: array + required: + - aliases + responses: {} + summary: '' + tags: [] + /api/spaces/_get_shareable_references: + post: + description: Get shareable references + operationId: '%2Fapi%2Fspaces%2F_get_shareable_references#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + objects: + items: + additionalProperties: false + type: object + properties: + id: + type: string + type: + type: string + required: + - type + - id + type: array + required: + - objects + responses: {} + summary: '' + tags: [] + /api/spaces/_resolve_copy_saved_objects_errors: + post: + description: Resolve conflicts copying saved objects + operationId: '%2Fapi%2Fspaces%2F_resolve_copy_saved_objects_errors#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + compatibilityMode: + default: false + type: boolean + createNewCopies: + default: true + type: boolean + includeReferences: + default: false + type: boolean + objects: + items: + additionalProperties: false + type: object + properties: + id: + type: string + type: + type: string + required: + - type + - id + type: array + retries: + additionalProperties: + items: + additionalProperties: false + type: object + properties: + createNewCopy: + type: boolean + destinationId: + type: string + id: + type: string + ignoreMissingReferences: + type: boolean + overwrite: + default: false + type: boolean + type: + type: string + required: + - type + - id + type: array + type: object + required: + - retries + - objects + responses: {} + summary: '' + tags: [] + /api/spaces/_update_objects_spaces: + post: + description: Update saved objects in spaces + operationId: '%2Fapi%2Fspaces%2F_update_objects_spaces#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + objects: + items: + additionalProperties: false + type: object + properties: + id: + type: string + type: + type: string + required: + - type + - id + type: array + spacesToAdd: + items: + type: string + type: array + spacesToRemove: + items: + type: string + type: array + required: + - objects + - spacesToAdd + - spacesToRemove + responses: {} + summary: '' + tags: [] + /api/spaces/space: + get: + description: Get all spaces + operationId: '%2Fapi%2Fspaces%2Fspace#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: query + name: purpose + required: false + schema: + enum: + - any + - copySavedObjectsIntoSpace + - shareSavedObjectsIntoSpace + type: string + - in: query + name: include_authorized_purposes + required: true + schema: + anyOf: + - items: {} + type: array + - type: boolean + - type: number + - type: object + - type: string + nullable: true + oneOf: + - enum: + - false + type: boolean + x-oas-optional: true + - type: boolean + x-oas-optional: true + responses: {} + summary: '' + tags: + - spaces + post: + description: Create a space + operationId: '%2Fapi%2Fspaces%2Fspace#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _reserved: + type: boolean + color: + type: string + description: + type: string + disabledFeatures: + default: [] + items: + type: string + type: array + id: + type: string + imageUrl: + type: string + initials: + maxLength: 2 + type: string + name: + minLength: 1 + type: string + solution: + enum: + - security + - oblt + - es + - classic + type: string + required: + - id + - name + responses: {} + summary: '' + tags: + - spaces + /api/spaces/space/{id}: + delete: + description: Delete a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#2' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: id + required: true + schema: + type: string + responses: {} + summary: '' + tags: + - spaces + get: + description: Get a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#0' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - in: path + name: id + required: true + schema: + type: string + responses: {} + summary: '' + tags: + - spaces + put: + description: Update a space + operationId: '%2Fapi%2Fspaces%2Fspace%2F%7Bid%7D#1' + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _reserved: + type: boolean + color: + type: string + description: + type: string + disabledFeatures: + default: [] + items: + type: string + type: array + id: + type: string + imageUrl: + type: string + initials: + maxLength: 2 + type: string + name: + minLength: 1 + type: string + solution: + enum: + - security + - oblt + - es + - classic + type: string + required: + - id + - name + responses: {} + summary: '' + tags: + - spaces /api/status: get: operationId: '%2Fapi%2Fstatus#0' @@ -23448,6 +24391,7 @@ tags: - name: Fleet uninstall tokens - description: Machine learning name: ml + - name: roles - description: > Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects @@ -23474,4 +24418,5 @@ tags: x-displayName: Saved objects - description: SLO APIs enable you to define, manage and track service-level objectives name: slo + - name: spaces - name: system diff --git a/packages/core/http/core-http-browser/src/types.ts b/packages/core/http/core-http-browser/src/types.ts index 0d607274fce4a..6a1e0a7c358e0 100644 --- a/packages/core/http/core-http-browser/src/types.ts +++ b/packages/core/http/core-http-browser/src/types.ts @@ -322,7 +322,10 @@ export interface HttpFetchOptions extends HttpRequestInit { context?: KibanaExecutionContext; - /** @experimental */ + /** + * When defined, the API version string used to populate the ELASTIC_HTTP_VERSION_HEADER. + * Defaults to undefined. + */ version?: ApiVersion; } diff --git a/packages/kbn-ftr-common-functional-ui-services/services/security/role.ts b/packages/kbn-ftr-common-functional-ui-services/services/security/role.ts index 88ea94439984f..7f3ca86d8248c 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/security/role.ts +++ b/packages/kbn-ftr-common-functional-ui-services/services/security/role.ts @@ -16,15 +16,19 @@ export class Role { public async create(name: string, role: any) { this.log.debug(`creating role ${name}`); - const { data, status, statusText } = await this.kibanaServer.request({ - path: `/api/security/role/${name}`, - method: 'PUT', - body: { - kibana: role.kibana, - elasticsearch: role.elasticsearch, - }, - retries: 0, - }); + const { data, status, statusText } = await this.kibanaServer + .request({ + path: `/api/security/role/${name}`, + method: 'PUT', + body: { + kibana: role.kibana, + elasticsearch: role.elasticsearch, + }, + retries: 0, + }) + .catch((e) => { + throw new Error(util.inspect(e.axiosError.response, true)); + }); if (status !== 204) { throw new Error( `Expected status code of 204, received ${status} ${statusText}: ${util.inspect(data)}` diff --git a/packages/kbn-ftr-common-functional-ui-services/tsconfig.json b/packages/kbn-ftr-common-functional-ui-services/tsconfig.json index 555e696f0ca51..f1436196d3f21 100644 --- a/packages/kbn-ftr-common-functional-ui-services/tsconfig.json +++ b/packages/kbn-ftr-common-functional-ui-services/tsconfig.json @@ -13,6 +13,6 @@ "@kbn/test-subj-selector", "@kbn/ftr-common-functional-services", "@kbn/std", - "@kbn/expect" + "@kbn/expect", ] } diff --git a/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts b/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts index 785d6efc085ff..930fc91037d1a 100644 --- a/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts +++ b/packages/kbn-test/src/functional_tests/run_tests/run_tests.ts @@ -120,6 +120,11 @@ export async function runTests(log: ToolingLog, options: RunTestsOptions) { logsDir: options.logsDir, installDir: options.installDir, onEarlyExit, + extraKbnOpts: [ + config.get('serverless') + ? '--server.versioned.versionResolution=newest' + : '--server.versioned.versionResolution=oldest', + ], }); if (abortCtrl.signal.aborted) { diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index 8d0237916bb5a..3a9b20bbb0bd7 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -116,3 +116,14 @@ export const IMAGE_FILE_TYPES = ['image/svg+xml', 'image/jpeg', 'image/png', 'im * Prefix for API actions. */ export const API_OPERATION_PREFIX = 'api:'; + +/** + * The API version numbers used with the versioned router. + */ +export const API_VERSIONS = { + roles: { + public: { + v1: '2023-10-31', + }, + }, +}; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index 270ed24e1ad2d..9c0adb9e0d782 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -46,6 +46,7 @@ import { toMountPoint } from '@kbn/react-kibana-mount'; import type { Cluster } from '@kbn/remote-clusters-plugin/public'; import { REMOTE_CLUSTERS_PATH } from '@kbn/remote-clusters-plugin/public'; import { KibanaPrivileges } from '@kbn/security-role-management-model'; +import { API_VERSIONS as SPACES_API_VERSIONS } from '@kbn/spaces-plugin/common'; import type { Space, SpacesApiUi } from '@kbn/spaces-plugin/public'; import type { PublicMethodsOf } from '@kbn/utility-types'; @@ -272,7 +273,7 @@ function useRole( function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup) { const [spaces, setSpaces] = useState<{ enabled: boolean; list: Space[] } | null>(null); useEffect(() => { - http.get('/api/spaces/space').then( + http.get('/api/spaces/space', { version: SPACES_API_VERSIONS.public.v1 }).then( (fetchedSpaces) => setSpaces({ enabled: true, list: fetchedSpaces }), (err: IHttpFetchError) => { // Spaces plugin can be disabled and hence this endpoint can be unavailable. diff --git a/x-pack/plugins/security/public/management/roles/roles_api_client.ts b/x-pack/plugins/security/public/management/roles/roles_api_client.ts index d6dcab658d21c..5c3970e82c516 100644 --- a/x-pack/plugins/security/public/management/roles/roles_api_client.ts +++ b/x-pack/plugins/security/public/management/roles/roles_api_client.ts @@ -9,25 +9,31 @@ import type { HttpStart } from '@kbn/core/public'; import type { BulkUpdatePayload, BulkUpdateRoleResponse } from '@kbn/security-plugin-types-public'; import type { Role, RoleIndexPrivilege, RoleRemoteIndexPrivilege } from '../../../common'; +import { API_VERSIONS } from '../../../common/constants'; import { copyRole } from '../../../common/model'; +const version = API_VERSIONS.roles.public.v1; + export class RolesAPIClient { constructor(private readonly http: HttpStart) {} public getRoles = async () => { - return await this.http.get('/api/security/role'); + return await this.http.get('/api/security/role', { version }); }; public getRole = async (roleName: string) => { - return await this.http.get(`/api/security/role/${encodeURIComponent(roleName)}`); + return await this.http.get(`/api/security/role/${encodeURIComponent(roleName)}`, { + version, + }); }; public deleteRole = async (roleName: string) => { - await this.http.delete(`/api/security/role/${encodeURIComponent(roleName)}`); + await this.http.delete(`/api/security/role/${encodeURIComponent(roleName)}`, { version }); }; public saveRole = async ({ role, createOnly = false }: { role: Role; createOnly?: boolean }) => { await this.http.put(`/api/security/role/${encodeURIComponent(role.name)}`, { + version, body: JSON.stringify(this.transformRoleForSave(copyRole(role))), query: { createOnly }, }); @@ -37,6 +43,7 @@ export class RolesAPIClient { rolesUpdate, }: BulkUpdatePayload): Promise => { return await this.http.post('/api/security/roles', { + version, body: JSON.stringify({ roles: Object.fromEntries( rolesUpdate.map((role) => [role.name, this.transformRoleForSave(copyRole(role))]) diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts index 223949843fee5..5c6f3bff716fc 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts @@ -9,9 +9,11 @@ import Boom from '@hapi/boom'; import { kibanaResponseFactory } from '@kbn/core/server'; import { coreMock, httpServerMock } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; import type { LicenseCheck } from '@kbn/licensing-plugin/server'; import { defineDeleteRolesRoutes } from './delete'; +import { API_VERSIONS } from '../../../../common/constants'; import { routeDefinitionParamsMock } from '../../index.mock'; interface TestOptions { @@ -28,6 +30,8 @@ describe('DELETE role', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const versionedRouterMock = mockRouteDefinitionParams.router + .versioned as MockedVersionedRouter; const mockCoreContext = coreMock.createRequestHandlerContext(); const mockLicensingContext = { license: { check: jest.fn().mockReturnValue(licenseCheckResult) }, @@ -44,7 +48,9 @@ describe('DELETE role', () => { } defineDeleteRolesRoutes(mockRouteDefinitionParams); - const [[, handler]] = mockRouteDefinitionParams.router.delete.mock.calls; + const handler = versionedRouterMock.getRoute('delete', '/api/security/role/{name}').versions[ + API_VERSIONS.roles.public.v1 + ].handler; const headers = { authorization: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts index 022e574181425..fe7c97b32d27b 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts @@ -8,32 +8,40 @@ import { schema } from '@kbn/config-schema'; import type { RouteDefinitionParams } from '../..'; +import { API_VERSIONS } from '../../../../common/constants'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; export function defineDeleteRolesRoutes({ router }: RouteDefinitionParams) { - router.delete( - { + router.versioned + .delete({ path: '/api/security/role/{name}', + access: 'public', + summary: `Delete a role`, options: { - access: 'public', - summary: `Delete a role`, + tags: ['oas-tag:roles'], }, - validate: { - params: schema.object({ name: schema.string({ minLength: 1 }) }), + }) + .addVersion( + { + version: API_VERSIONS.roles.public.v1, + validate: { + request: { + params: schema.object({ name: schema.string({ minLength: 1 }) }), + }, + }, }, - }, - createLicensedRouteHandler(async (context, request, response) => { - try { - const esClient = (await context.core).elasticsearch.client; - await esClient.asCurrentUser.security.deleteRole({ - name: request.params.name, - }); + createLicensedRouteHandler(async (context, request, response) => { + try { + const esClient = (await context.core).elasticsearch.client; + await esClient.asCurrentUser.security.deleteRole({ + name: request.params.name, + }); - return response.noContent(); - } catch (error) { - return response.customError(wrapIntoCustomErrorResponse(error)); - } - }) - ); + return response.noContent(); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); } diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index b09743fd077e2..732fecb6b5372 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -9,9 +9,11 @@ import Boom from '@hapi/boom'; import { kibanaResponseFactory } from '@kbn/core/server'; import { coreMock, httpServerMock } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; import type { LicenseCheck } from '@kbn/licensing-plugin/server'; import { defineGetRolesRoutes } from './get'; +import { API_VERSIONS } from '../../../../common/constants'; import { routeDefinitionParamsMock } from '../../index.mock'; const application = 'kibana-.kibana'; @@ -31,6 +33,8 @@ describe('GET role', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const versionedRouterMock = mockRouteDefinitionParams.router + .versioned as MockedVersionedRouter; mockRouteDefinitionParams.authz.applicationName = application; mockRouteDefinitionParams.getFeatures = jest.fn().mockResolvedValue([]); @@ -50,7 +54,9 @@ describe('GET role', () => { } defineGetRolesRoutes(mockRouteDefinitionParams); - const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + const handler = versionedRouterMock.getRoute('get', '/api/security/role/{name}').versions[ + API_VERSIONS.roles.public.v1 + ].handler; const headers = { authorization: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index ec2208341dc16..f36c785758976 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import type { RouteDefinitionParams } from '../..'; +import { API_VERSIONS } from '../../../../common/constants'; import { transformElasticsearchRoleToRole } from '../../../authorization'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; @@ -18,47 +19,54 @@ export function defineGetRolesRoutes({ getFeatures, logger, }: RouteDefinitionParams) { - router.get( - { + router.versioned + .get({ path: '/api/security/role/{name}', + access: 'public', + summary: `Get a role`, options: { - access: 'public', - summary: `Get a role`, + tags: ['oas-tag:roles'], }, - validate: { - params: schema.object({ name: schema.string({ minLength: 1 }) }), + }) + .addVersion( + { + version: API_VERSIONS.roles.public.v1, + validate: { + request: { + params: schema.object({ name: schema.string({ minLength: 1 }) }), + }, + }, }, - }, - createLicensedRouteHandler(async (context, request, response) => { - try { - const esClient = (await context.core).elasticsearch.client; + createLicensedRouteHandler(async (context, request, response) => { + try { + const esClient = (await context.core).elasticsearch.client; - const [features, elasticsearchRoles] = await Promise.all([ - getFeatures(), - await esClient.asCurrentUser.security.getRole({ - name: request.params.name, - }), - ]); + const [features, elasticsearchRoles] = await Promise.all([ + getFeatures(), + await esClient.asCurrentUser.security.getRole({ + name: request.params.name, + }), + ]); - const elasticsearchRole = elasticsearchRoles[request.params.name]; + const elasticsearchRole = elasticsearchRoles[request.params.name]; - if (elasticsearchRole) { - return response.ok({ - body: transformElasticsearchRoleToRole( - features, - // @ts-expect-error `SecurityIndicesPrivileges.names` expected to be `string[]` - elasticsearchRole, - request.params.name, - authz.applicationName, - logger - ), - }); - } + if (elasticsearchRole) { + return response.ok({ + body: transformElasticsearchRoleToRole( + features, + // @ts-expect-error `SecurityIndicesPrivileges.names` expected to be `string[]` + elasticsearchRole, + request.params.name, + authz.applicationName, + logger + ), + }); + } - return response.notFound(); - } catch (error) { - return response.customError(wrapIntoCustomErrorResponse(error)); - } - }) - ); + return response.notFound(); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); } diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index 3fe91ded3342d..26f48e9230b4a 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -9,9 +9,11 @@ import Boom from '@hapi/boom'; import { kibanaResponseFactory } from '@kbn/core/server'; import { coreMock, httpServerMock } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; import type { LicenseCheck } from '@kbn/licensing-plugin/server'; import { defineGetAllRolesRoutes } from './get_all'; +import { API_VERSIONS } from '../../../../common/constants'; import { routeDefinitionParamsMock } from '../../index.mock'; const application = 'kibana-.kibana'; @@ -31,6 +33,8 @@ describe('GET all roles', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const versionedRouterMock = mockRouteDefinitionParams.router + .versioned as MockedVersionedRouter; mockRouteDefinitionParams.authz.applicationName = application; mockRouteDefinitionParams.getFeatures = jest.fn().mockResolvedValue([]); @@ -50,7 +54,9 @@ describe('GET all roles', () => { } defineGetAllRolesRoutes(mockRouteDefinitionParams); - const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + const handler = versionedRouterMock.getRoute('get', '/api/security/role').versions[ + API_VERSIONS.roles.public.v1 + ].handler; const headers = { authorization: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index ed31aedba7f31..07e9a953be8fb 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -6,6 +6,7 @@ */ import type { RouteDefinitionParams } from '../..'; +import { API_VERSIONS } from '../../../../common/constants'; import { compareRolesByName, transformElasticsearchRoleToRole } from '../../../authorization'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; @@ -18,45 +19,50 @@ export function defineGetAllRolesRoutes({ buildFlavor, config, }: RouteDefinitionParams) { - router.get( - { + router.versioned + .get({ path: '/api/security/role', + access: 'public', + summary: `Get all roles`, options: { - access: 'public', - summary: `Get all roles`, + tags: ['oas-tag:roles'], }, - validate: false, - }, - createLicensedRouteHandler(async (context, request, response) => { - try { - const hideReservedRoles = buildFlavor === 'serverless'; - const esClient = (await context.core).elasticsearch.client; - const [features, elasticsearchRoles] = await Promise.all([ - getFeatures(), - await esClient.asCurrentUser.security.getRole(), - ]); + }) + .addVersion( + { + version: API_VERSIONS.roles.public.v1, + validate: false, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const hideReservedRoles = buildFlavor === 'serverless'; + const esClient = (await context.core).elasticsearch.client; + const [features, elasticsearchRoles] = await Promise.all([ + getFeatures(), + await esClient.asCurrentUser.security.getRole(), + ]); - // Transform elasticsearch roles into Kibana roles and return in a list sorted by the role name. - return response.ok({ - body: Object.entries(elasticsearchRoles) - .map(([roleName, elasticsearchRole]) => - transformElasticsearchRoleToRole( - features, - // @ts-expect-error @elastic/elasticsearch SecurityIndicesPrivileges.names expected to be string[] - elasticsearchRole, - roleName, - authz.applicationName, - logger + // Transform elasticsearch roles into Kibana roles and return in a list sorted by the role name. + return response.ok({ + body: Object.entries(elasticsearchRoles) + .map(([roleName, elasticsearchRole]) => + transformElasticsearchRoleToRole( + features, + // @ts-expect-error @elastic/elasticsearch SecurityIndicesPrivileges.names expected to be string[] + elasticsearchRole, + roleName, + authz.applicationName, + logger + ) ) - ) - .filter((role) => { - return !hideReservedRoles || !role.metadata?._reserved; - }) - .sort(compareRolesByName), - }); - } catch (error) { - return response.customError(wrapIntoCustomErrorResponse(error)); - } - }) - ); + .filter((role) => { + return !hideReservedRoles || !role.metadata?._reserved; + }) + .sort(compareRolesByName), + }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); } diff --git a/x-pack/plugins/security/server/routes/authorization/roles/post.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/post.test.ts index 0ef752423606d..c28a036a676b6 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/post.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/post.test.ts @@ -7,12 +7,14 @@ import { kibanaResponseFactory } from '@kbn/core/server'; import { coreMock, httpServerMock } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; import { KibanaFeature } from '@kbn/features-plugin/server'; import type { LicenseCheck } from '@kbn/licensing-plugin/server'; import { GLOBAL_RESOURCE } from '@kbn/security-plugin-types-server'; import type { BulkCreateOrUpdateRolesPayloadSchemaType } from './model/bulk_create_or_update_payload'; import { defineBulkCreateOrUpdateRolesRoutes } from './post'; +import { API_VERSIONS } from '../../../../common/constants'; import { securityFeatureUsageServiceMock } from '../../../feature_usage/index.mock'; import { routeDefinitionParamsMock } from '../../index.mock'; @@ -89,6 +91,7 @@ const postRolesTest = ( ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const versionedRouterMock = mockRouteDefinitionParams.router.versioned as MockedVersionedRouter; mockRouteDefinitionParams.authz.applicationName = application; mockRouteDefinitionParams.authz.privileges.get.mockReturnValue(privilegeMap); @@ -158,13 +161,15 @@ const postRolesTest = ( ); defineBulkCreateOrUpdateRolesRoutes(mockRouteDefinitionParams); - const [[{ validate }, handler]] = mockRouteDefinitionParams.router.post.mock.calls; + const { handler, config } = versionedRouterMock.getRoute('post', '/api/security/roles') + .versions[API_VERSIONS.roles.public.v1]; const headers = { authorization: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ method: 'post', path: '/api/security/roles', - body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, + body: + payload !== undefined ? (config.validate as any).request.body.validate(payload) : undefined, headers, }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/post.ts b/x-pack/plugins/security/server/routes/authorization/roles/post.ts index 37967d208bf3a..0fe918ee5cc3e 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/post.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/post.ts @@ -11,6 +11,7 @@ import { transformPutPayloadToElasticsearchRole, } from './model'; import type { RouteDefinitionParams } from '../..'; +import { API_VERSIONS } from '../../../../common/constants'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { validateKibanaPrivileges } from '../../../lib'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; @@ -39,97 +40,104 @@ export function defineBulkCreateOrUpdateRolesRoutes({ getFeatures, getFeatureUsageService, }: RouteDefinitionParams) { - router.post( - { + router.versioned + .post({ path: '/api/security/roles', + access: 'public', + summary: 'Create or update roles', options: { - access: 'public', - summary: 'Create or update roles', + tags: ['oas-tag:roles'], }, - validate: { - body: getBulkCreateOrUpdatePayloadSchema(() => { - const privileges = authz.privileges.get(); - return { - global: Object.keys(privileges.global), - space: Object.keys(privileges.space), - }; - }), + }) + .addVersion( + { + version: API_VERSIONS.roles.public.v1, + validate: { + request: { + body: getBulkCreateOrUpdatePayloadSchema(() => { + const privileges = authz.privileges.get(); + return { + global: Object.keys(privileges.global), + space: Object.keys(privileges.space), + }; + }), + }, + }, }, - }, - createLicensedRouteHandler(async (context, request, response) => { - try { - const esClient = (await context.core).elasticsearch.client; - const features = await getFeatures(); + createLicensedRouteHandler(async (context, request, response) => { + try { + const esClient = (await context.core).elasticsearch.client; + const features = await getFeatures(); - const { roles } = request.body; - const validatedRolesNames = []; - const kibanaErrors: RolesErrorsDetails = {}; + const { roles } = request.body; + const validatedRolesNames = []; + const kibanaErrors: RolesErrorsDetails = {}; - for (const [roleName, role] of Object.entries(roles)) { - const { validationErrors } = validateKibanaPrivileges(features, role.kibana); + for (const [roleName, role] of Object.entries(roles)) { + const { validationErrors } = validateKibanaPrivileges(features, role.kibana); - if (validationErrors.length) { - kibanaErrors[roleName] = { - type: 'kibana_privilege_validation_exception', - reason: `Role cannot be updated due to validation errors: ${JSON.stringify( - validationErrors - )}`, - }; + if (validationErrors.length) { + kibanaErrors[roleName] = { + type: 'kibana_privilege_validation_exception', + reason: `Role cannot be updated due to validation errors: ${JSON.stringify( + validationErrors + )}`, + }; - continue; - } + continue; + } - validatedRolesNames.push(roleName); - } + validatedRolesNames.push(roleName); + } - const rawRoles = await esClient.asCurrentUser.security.getRole( - { name: validatedRolesNames.join(',') }, - { ignore: [404] } - ); + const rawRoles = await esClient.asCurrentUser.security.getRole( + { name: validatedRolesNames.join(',') }, + { ignore: [404] } + ); - const esRolesPayload = Object.fromEntries( - validatedRolesNames.map((roleName) => [ - roleName, - transformPutPayloadToElasticsearchRole( - roles[roleName], - authz.applicationName, - rawRoles[roleName] ? rawRoles[roleName].applications : [] - ), - ]) - ); + const esRolesPayload = Object.fromEntries( + validatedRolesNames.map((roleName) => [ + roleName, + transformPutPayloadToElasticsearchRole( + roles[roleName], + authz.applicationName, + rawRoles[roleName] ? rawRoles[roleName].applications : [] + ), + ]) + ); - const esResponse = await esClient.asCurrentUser.transport.request({ - method: 'POST', - path: '/_security/role', - body: { roles: esRolesPayload }, - }); + const esResponse = await esClient.asCurrentUser.transport.request({ + method: 'POST', + path: '/_security/role', + body: { roles: esRolesPayload }, + }); - for (const roleName of [ - ...(esResponse.created ?? []), - ...(esResponse.updated ?? []), - ...(esResponse.noop ?? []), - ]) { - if (roleGrantsSubFeaturePrivileges(features, roles[roleName])) { - getFeatureUsageService().recordSubFeaturePrivilegeUsage(); + for (const roleName of [ + ...(esResponse.created ?? []), + ...(esResponse.updated ?? []), + ...(esResponse.noop ?? []), + ]) { + if (roleGrantsSubFeaturePrivileges(features, roles[roleName])) { + getFeatureUsageService().recordSubFeaturePrivilegeUsage(); + } } - } - const { created, noop, updated, errors: esErrors } = esResponse; - const hasAnyErrors = Object.keys(kibanaErrors).length || esErrors?.count; + const { created, noop, updated, errors: esErrors } = esResponse; + const hasAnyErrors = Object.keys(kibanaErrors).length || esErrors?.count; - return response.ok({ - body: { - created, - noop, - updated, - ...(hasAnyErrors && { - errors: { ...kibanaErrors, ...(esErrors?.details ?? {}) }, - }), - }, - }); - } catch (error) { - return response.customError(wrapIntoCustomErrorResponse(error)); - } - }) - ); + return response.ok({ + body: { + created, + noop, + updated, + ...(hasAnyErrors && { + errors: { ...kibanaErrors, ...(esErrors?.details ?? {}) }, + }), + }, + }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); } diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index 642aec90c4748..50665e0494b81 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -8,11 +8,13 @@ import type { Type } from '@kbn/config-schema'; import { kibanaResponseFactory } from '@kbn/core/server'; import { coreMock, httpServerMock } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; import { KibanaFeature } from '@kbn/features-plugin/server'; import type { LicenseCheck } from '@kbn/licensing-plugin/server'; import { GLOBAL_RESOURCE } from '@kbn/security-plugin-types-server'; import { definePutRolesRoutes } from './put'; +import { API_VERSIONS } from '../../../../common/constants'; import { securityFeatureUsageServiceMock } from '../../../feature_usage/index.mock'; import { routeDefinitionParamsMock } from '../../index.mock'; @@ -74,6 +76,7 @@ const putRoleTest = ( ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const versionedRouterMock = mockRouteDefinitionParams.router.versioned as MockedVersionedRouter; mockRouteDefinitionParams.authz.applicationName = application; mockRouteDefinitionParams.authz.privileges.get.mockReturnValue(privilegeMap); @@ -143,7 +146,8 @@ const putRoleTest = ( ); definePutRolesRoutes(mockRouteDefinitionParams); - const [[{ validate }, handler]] = mockRouteDefinitionParams.router.put.mock.calls; + const { handler, config } = versionedRouterMock.getRoute('put', '/api/security/role/{name}') + .versions[API_VERSIONS.roles.public.v1]; const headers = { authorization: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ @@ -151,7 +155,8 @@ const putRoleTest = ( path: `/api/security/role/${name}`, query: { createOnly }, params: { name }, - body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, + body: + payload !== undefined ? (config.validate as any).request.body.validate(payload) : undefined, headers, }); @@ -188,11 +193,15 @@ describe('PUT role', () => { let requestParamsSchema: Type; beforeEach(() => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const versionedRouterMock = mockRouteDefinitionParams.router + .versioned as MockedVersionedRouter; mockRouteDefinitionParams.authz.privileges.get.mockReturnValue(privilegeMap); definePutRolesRoutes(mockRouteDefinitionParams); - const [[{ validate }]] = mockRouteDefinitionParams.router.put.mock.calls; - requestParamsSchema = (validate as any).params; + const { config } = versionedRouterMock.getRoute('put', '/api/security/role/{name}').versions[ + API_VERSIONS.roles.public.v1 + ]; + requestParamsSchema = (config.validate as any).request.params; }); test('requires name in params', () => { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index 6175ba6f4d64f..16e2ab819e781 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -10,6 +10,7 @@ import { schema } from '@kbn/config-schema'; import { roleGrantsSubFeaturePrivileges } from './lib'; import { getPutPayloadSchema, transformPutPayloadToElasticsearchRole } from './model'; import type { RouteDefinitionParams } from '../..'; +import { API_VERSIONS } from '../../../../common/constants'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { validateKibanaPrivileges } from '../../../lib'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; @@ -20,75 +21,85 @@ export function definePutRolesRoutes({ getFeatures, getFeatureUsageService, }: RouteDefinitionParams) { - router.put( - { + router.versioned + .put({ path: '/api/security/role/{name}', + access: 'public', + summary: `Create or update a role`, options: { - access: 'public', - summary: `Create or update a role`, + tags: ['oas-tag:roles'], }, - validate: { - params: schema.object({ name: schema.string({ minLength: 1, maxLength: 1024 }) }), - query: schema.object({ createOnly: schema.boolean({ defaultValue: false }) }), - body: getPutPayloadSchema(() => { - const privileges = authz.privileges.get(); - return { - global: Object.keys(privileges.global), - space: Object.keys(privileges.space), - }; - }), + }) + .addVersion( + { + version: API_VERSIONS.roles.public.v1, + validate: { + request: { + params: schema.object({ name: schema.string({ minLength: 1, maxLength: 1024 }) }), + query: schema.object({ createOnly: schema.boolean({ defaultValue: false }) }), + body: getPutPayloadSchema(() => { + const privileges = authz.privileges.get(); + return { + global: Object.keys(privileges.global), + space: Object.keys(privileges.space), + }; + }), + }, + }, }, - }, - createLicensedRouteHandler(async (context, request, response) => { - const { name } = request.params; - const { createOnly } = request.query; - try { - const esClient = (await context.core).elasticsearch.client; + createLicensedRouteHandler(async (context, request, response) => { + const { name } = request.params; + const { createOnly } = request.query; + try { + const esClient = (await context.core).elasticsearch.client; - const [features, rawRoles] = await Promise.all([ - getFeatures(), - esClient.asCurrentUser.security.getRole({ name: request.params.name }, { ignore: [404] }), - ]); + const [features, rawRoles] = await Promise.all([ + getFeatures(), + esClient.asCurrentUser.security.getRole( + { name: request.params.name }, + { ignore: [404] } + ), + ]); - const { validationErrors } = validateKibanaPrivileges(features, request.body.kibana); + const { validationErrors } = validateKibanaPrivileges(features, request.body.kibana); - if (validationErrors.length) { - return response.badRequest({ - body: { - message: `Role cannot be updated due to validation errors: ${JSON.stringify( - validationErrors - )}`, - }, - }); - } + if (validationErrors.length) { + return response.badRequest({ + body: { + message: `Role cannot be updated due to validation errors: ${JSON.stringify( + validationErrors + )}`, + }, + }); + } - if (createOnly && !!rawRoles[name]) { - return response.conflict({ - body: { - message: `Role already exists and cannot be created: ${name}`, - }, - }); - } + if (createOnly && !!rawRoles[name]) { + return response.conflict({ + body: { + message: `Role already exists and cannot be created: ${name}`, + }, + }); + } - const body = transformPutPayloadToElasticsearchRole( - request.body, - authz.applicationName, - rawRoles[name] ? rawRoles[name].applications : [] - ); + const body = transformPutPayloadToElasticsearchRole( + request.body, + authz.applicationName, + rawRoles[name] ? rawRoles[name].applications : [] + ); - await esClient.asCurrentUser.security.putRole({ - name: request.params.name, - body, - }); + await esClient.asCurrentUser.security.putRole({ + name: request.params.name, + body, + }); - if (roleGrantsSubFeaturePrivileges(features, request.body)) { - getFeatureUsageService().recordSubFeaturePrivilegeUsage(); - } + if (roleGrantsSubFeaturePrivileges(features, request.body)) { + getFeatureUsageService().recordSubFeaturePrivilegeUsage(); + } - return response.noContent(); - } catch (error) { - return response.customError(wrapIntoCustomErrorResponse(error)); - } - }) - ); + return response.noContent(); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); } diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index 21a32edcec212..535e221f8e5fb 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -86,6 +86,7 @@ "@kbn/security-authorization-core", "@kbn/security-role-management-model", "@kbn/security-ui-components", + "@kbn/core-http-router-server-mocks", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/spaces/common/constants.ts b/x-pack/plugins/spaces/common/constants.ts index bbbe38451fedf..14932a93a06b7 100644 --- a/x-pack/plugins/spaces/common/constants.ts +++ b/x-pack/plugins/spaces/common/constants.ts @@ -43,3 +43,12 @@ export const SOLUTION_VIEW_CLASSIC = 'classic' as const; export const FEATURE_PRIVILEGES_ALL = 'all' as const; export const FEATURE_PRIVILEGES_READ = 'read' as const; export const FEATURE_PRIVILEGES_CUSTOM = 'custom' as const; + +/** + * The API version numbers used with the versioned router. + */ +export const API_VERSIONS = { + public: { + v1: '2023-10-31', + }, +}; diff --git a/x-pack/plugins/spaces/common/index.ts b/x-pack/plugins/spaces/common/index.ts index 65342bf2e43f4..21fee91bf979d 100644 --- a/x-pack/plugins/spaces/common/index.ts +++ b/x-pack/plugins/spaces/common/index.ts @@ -11,6 +11,7 @@ export { SPACE_SEARCH_COUNT_THRESHOLD, ENTER_SPACE_PATH, DEFAULT_SPACE_ID, + API_VERSIONS, } from './constants'; export { addSpaceIdToPath, getSpaceIdFromPath } from './lib/spaces_url_parser'; export type { diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts index 962f02ca2bd79..d2f2681bd1809 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts @@ -13,7 +13,12 @@ import type { SavedObjectsCollectMultiNamespaceReferencesResponse } from '@kbn/c import type { LegacyUrlAliasTarget } from '@kbn/core-saved-objects-common'; import type { Role } from '@kbn/security-plugin-types-common'; -import type { GetAllSpacesOptions, GetSpaceResult, Space } from '../../common'; +import { + API_VERSIONS, + type GetAllSpacesOptions, + type GetSpaceResult, + type Space, +} from '../../common'; import type { CopySavedObjectsToSpaceResponse } from '../copy_saved_objects_to_space/types'; import type { SpaceContentTypeSummaryItem } from '../types'; @@ -23,6 +28,7 @@ interface SavedObjectTarget { } const TAG_TYPE = 'tag'; +const version = API_VERSIONS.public.v1; export class SpacesManager { private activeSpace$: BehaviorSubject = new BehaviorSubject(null); @@ -49,11 +55,11 @@ export class SpacesManager { public async getSpaces(options: GetAllSpacesOptions = {}): Promise { const { purpose, includeAuthorizedPurposes } = options; const query = { purpose, include_authorized_purposes: includeAuthorizedPurposes }; - return await this.http.get('/api/spaces/space', { query }); + return await this.http.get('/api/spaces/space', { query, version }); } public async getSpace(id: string): Promise { - return await this.http.get(`/api/spaces/space/${encodeURIComponent(id)}`); + return await this.http.get(`/api/spaces/space/${encodeURIComponent(id)}`, { version }); } public async getActiveSpace({ forceRefresh = false } = {}) { @@ -69,6 +75,7 @@ export class SpacesManager { public async createSpace(space: Space) { await this.http.post(`/api/spaces/space`, { body: JSON.stringify(space), + version, }); } @@ -78,6 +85,7 @@ export class SpacesManager { overwrite: true, }, body: JSON.stringify(space), + version, }); const activeSpaceId = (await this.getActiveSpace()).id; @@ -88,7 +96,7 @@ export class SpacesManager { } public async deleteSpace(space: Space) { - await this.http.delete(`/api/spaces/space/${encodeURIComponent(space.id)}`); + await this.http.delete(`/api/spaces/space/${encodeURIComponent(space.id)}`, { version }); } public async disableLegacyUrlAliases(aliases: LegacyUrlAliasTarget[]) { diff --git a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts index f50b73d7f8513..c3c13eeff04bb 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts @@ -16,9 +16,11 @@ import { httpServiceMock, loggingSystemMock, } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { initDeleteSpacesApi } from './delete'; +import { API_VERSIONS } from '../../../../common'; import { spacesConfig } from '../../../lib/__fixtures__'; import { SpacesClientService } from '../../../spaces_client'; import { SpacesService } from '../../../spaces_service'; @@ -36,7 +38,7 @@ describe('Spaces Public API', () => { const setup = async () => { const httpService = httpServiceMock.createSetupContract(); const router = httpService.createRouter(); - + const versionedRouterMock = router.versioned as MockedVersionedRouter; const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects); const log = loggingSystemMock.create().get('spaces'); @@ -71,10 +73,13 @@ describe('Spaces Public API', () => { isServerless: false, }); - const [routeDefinition, routeHandler] = router.delete.mock.calls[0]; + const { handler: routeHandler, config } = versionedRouterMock.getRoute( + 'delete', + '/api/spaces/space/{id}' + ).versions[API_VERSIONS.public.v1]; return { - routeValidation: routeDefinition.validate as RouteValidatorConfig<{}, {}, {}>, + routeValidation: (config.validate as any).request as RouteValidatorConfig<{}, {}, {}>, routeHandler, savedObjectsRepositoryMock, }; diff --git a/x-pack/plugins/spaces/server/routes/api/external/delete.ts b/x-pack/plugins/spaces/server/routes/api/external/delete.ts index 6ede0fa220043..515f8811e5dcf 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/delete.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/delete.ts @@ -11,47 +11,55 @@ import { schema } from '@kbn/config-schema'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { ExternalRouteDeps } from '.'; +import { API_VERSIONS } from '../../../../common'; import { wrapError } from '../../../lib/errors'; import { createLicensedRouteHandler } from '../../lib'; export function initDeleteSpacesApi(deps: ExternalRouteDeps) { - const { router, log, getSpacesService, isServerless } = deps; + const { router, log, getSpacesService } = deps; - router.delete( - { + router.versioned + .delete({ path: '/api/spaces/space/{id}', + access: 'public', + description: `Delete a space`, options: { - access: isServerless ? 'internal' : 'public', - description: `Delete a space`, + tags: ['oas-tag:spaces'], }, - validate: { - params: schema.object({ - id: schema.string(), - }), + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + }, + }, }, - }, - createLicensedRouteHandler(async (context, request, response) => { - const spacesClient = getSpacesService().createSpacesClient(request); - - const id = request.params.id; - - try { - await spacesClient.delete(id); - } catch (error) { - if (SavedObjectsErrorHelpers.isNotFoundError(error)) { - return response.notFound(); - } else if (SavedObjectsErrorHelpers.isEsCannotExecuteScriptError(error)) { - log.error( - `Failed to delete space '${id}', cannot execute script in Elasticsearch query: ${error.message}` - ); - return response.customError( - wrapError(Boom.badRequest('Cannot execute script in Elasticsearch query')) - ); + createLicensedRouteHandler(async (context, request, response) => { + const spacesClient = getSpacesService().createSpacesClient(request); + + const id = request.params.id; + + try { + await spacesClient.delete(id); + } catch (error) { + if (SavedObjectsErrorHelpers.isNotFoundError(error)) { + return response.notFound(); + } else if (SavedObjectsErrorHelpers.isEsCannotExecuteScriptError(error)) { + log.error( + `Failed to delete space '${id}', cannot execute script in Elasticsearch query: ${error.message}` + ); + return response.customError( + wrapError(Boom.badRequest('Cannot execute script in Elasticsearch query')) + ); + } + return response.customError(wrapError(error)); } - return response.customError(wrapError(error)); - } - return response.noContent(); - }) - ); + return response.noContent(); + }) + ); } diff --git a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts index 3b5774284f19f..38f63202bc08b 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts @@ -14,9 +14,11 @@ import { httpServiceMock, loggingSystemMock, } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { initGetSpaceApi } from './get'; +import { API_VERSIONS } from '../../../../common'; import { spacesConfig } from '../../../lib/__fixtures__'; import { SpacesClientService } from '../../../spaces_client'; import { SpacesService } from '../../../spaces_service'; @@ -35,6 +37,7 @@ describe('GET space', () => { const setup = async () => { const httpService = httpServiceMock.createSetupContract(); const router = httpService.createRouter(); + const versionedRouterMock = router.versioned as MockedVersionedRouter; const coreStart = coreMock.createStart(); @@ -70,8 +73,12 @@ describe('GET space', () => { isServerless: false, }); + const { handler } = versionedRouterMock.getRoute('get', '/api/spaces/space/{id}').versions[ + API_VERSIONS.public.v1 + ]; + return { - routeHandler: router.get.mock.calls[0][1], + routeHandler: handler, }; }; diff --git a/x-pack/plugins/spaces/server/routes/api/external/get.ts b/x-pack/plugins/spaces/server/routes/api/external/get.ts index dce169449c99a..8d4e3c0c359ef 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get.ts @@ -9,40 +9,47 @@ import { schema } from '@kbn/config-schema'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { ExternalRouteDeps } from '.'; +import { API_VERSIONS } from '../../../../common'; import { wrapError } from '../../../lib/errors'; import { createLicensedRouteHandler } from '../../lib'; export function initGetSpaceApi(deps: ExternalRouteDeps) { - const { router, getSpacesService, isServerless } = deps; + const { router, getSpacesService } = deps; - router.get( - { + router.versioned + .get({ path: '/api/spaces/space/{id}', + access: 'public', + description: `Get a space`, options: { - access: isServerless ? 'internal' : 'public', - description: `Get a space`, + tags: ['oas-tag:spaces'], }, - validate: { - params: schema.object({ - id: schema.string(), - }), + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + }, + }, }, - }, - createLicensedRouteHandler(async (context, request, response) => { - const spaceId = request.params.id; - const spacesClient = getSpacesService().createSpacesClient(request); - - try { - const space = await spacesClient.get(spaceId); - return response.ok({ - body: space, - }); - } catch (error) { - if (SavedObjectsErrorHelpers.isNotFoundError(error)) { - return response.notFound(); + createLicensedRouteHandler(async (context, request, response) => { + const spaceId = request.params.id; + const spacesClient = getSpacesService().createSpacesClient(request); + try { + const space = await spacesClient.get(spaceId); + return response.ok({ + body: space, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isNotFoundError(error)) { + return response.notFound(); + } + return response.customError(wrapError(error)); } - return response.customError(wrapError(error)); - } - }) - ); + }) + ); } diff --git a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts index d2f8162a3f236..a1cbc729c999d 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts @@ -15,10 +15,13 @@ import { httpServiceMock, loggingSystemMock, } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; +import type { RouteValidatorConfig } from '@kbn/core-http-server'; import { getRequestValidation } from '@kbn/core-http-server'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { initGetAllSpacesApi } from './get_all'; +import { API_VERSIONS } from '../../../../common'; import { spacesConfig } from '../../../lib/__fixtures__'; import { SpacesClientService } from '../../../spaces_client'; import { SpacesService } from '../../../spaces_service'; @@ -37,6 +40,7 @@ describe('GET /spaces/space', () => { const setup = async () => { const httpService = httpServiceMock.createSetupContract(); const router = httpService.createRouter(); + const versionedRouterMock = router.versioned as MockedVersionedRouter; const coreStart = coreMock.createStart(); @@ -72,9 +76,13 @@ describe('GET /spaces/space', () => { isServerless: false, }); + const { handler, config } = versionedRouterMock.getRoute('get', '/api/spaces/space').versions[ + API_VERSIONS.public.v1 + ]; + return { - routeConfig: router.get.mock.calls[0][0], - routeHandler: router.get.mock.calls[0][1], + routeValidation: (config.validate as any).request as RouteValidatorConfig<{}, {}, {}> | false, + routeHandler: handler, }; }; @@ -92,17 +100,17 @@ describe('GET /spaces/space', () => { }); it(`returns expected result when specifying include_authorized_purposes=true`, async () => { - const { routeConfig, routeHandler } = await setup(); + const { routeValidation, routeHandler } = await setup(); const request = httpServerMock.createKibanaRequest({ method: 'get', query: { purpose, include_authorized_purposes: true }, }); - if (routeConfig.validate === false) { + if (routeValidation === false) { throw new Error('Test setup failure. Expected route validation'); } - const queryParamsValidation = getRequestValidation(routeConfig.validate) + const queryParamsValidation = getRequestValidation(routeValidation) .query! as ObjectType; const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); diff --git a/x-pack/plugins/spaces/server/routes/api/external/get_all.ts b/x-pack/plugins/spaces/server/routes/api/external/get_all.ts index 603dc3dfe45ba..baa47ca6956dc 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get_all.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get_all.ts @@ -8,63 +8,70 @@ import { schema } from '@kbn/config-schema'; import type { ExternalRouteDeps } from '.'; -import type { Space } from '../../../../common'; +import { API_VERSIONS, type Space } from '../../../../common'; import { wrapError } from '../../../lib/errors'; import { createLicensedRouteHandler } from '../../lib'; export function initGetAllSpacesApi(deps: ExternalRouteDeps) { - const { router, log, getSpacesService, isServerless } = deps; + const { router, log, getSpacesService } = deps; - router.get( - { + router.versioned + .get({ path: '/api/spaces/space', + access: 'public', + description: `Get all spaces`, options: { - access: isServerless ? 'internal' : 'public', - description: `Get all spaces`, + tags: ['oas-tag:spaces'], }, - validate: { - query: schema.object({ - purpose: schema.maybe( - schema.oneOf([ - schema.literal('any'), - schema.literal('copySavedObjectsIntoSpace'), - schema.literal('shareSavedObjectsIntoSpace'), - ]) - ), - include_authorized_purposes: schema.conditional( - schema.siblingRef('purpose'), - schema.string(), - schema.maybe(schema.literal(false)), - schema.maybe(schema.boolean()) - ), - }), + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + query: schema.object({ + purpose: schema.maybe( + schema.oneOf([ + schema.literal('any'), + schema.literal('copySavedObjectsIntoSpace'), + schema.literal('shareSavedObjectsIntoSpace'), + ]) + ), + include_authorized_purposes: schema.conditional( + schema.siblingRef('purpose'), + schema.string(), + schema.maybe(schema.literal(false)), + schema.maybe(schema.boolean()) + ), + }), + }, + }, }, - }, - createLicensedRouteHandler(async (context, request, response) => { - log.debug(`Inside GET /api/spaces/space`); + createLicensedRouteHandler(async (context, request, response) => { + log.debug(`Inside GET /api/spaces/space`); - const { purpose, include_authorized_purposes: includeAuthorizedPurposes } = request.query; + const { purpose, include_authorized_purposes: includeAuthorizedPurposes } = request.query; - const spacesClient = getSpacesService().createSpacesClient(request); + const spacesClient = getSpacesService().createSpacesClient(request); - let spaces: Space[]; + let spaces: Space[]; - try { - log.debug( - `Attempting to retrieve all spaces for ${purpose} purpose with includeAuthorizedPurposes=${includeAuthorizedPurposes}` - ); - spaces = await spacesClient.getAll({ purpose, includeAuthorizedPurposes }); - log.debug( - `Retrieved ${spaces.length} spaces for ${purpose} purpose with includeAuthorizedPurposes=${includeAuthorizedPurposes}` - ); - } catch (error) { - log.debug( - `Error retrieving spaces for ${purpose} purpose with includeAuthorizedPurposes=${includeAuthorizedPurposes}: ${error}` - ); - return response.customError(wrapError(error)); - } + try { + log.debug( + `Attempting to retrieve all spaces for ${purpose} purpose with includeAuthorizedPurposes=${includeAuthorizedPurposes}` + ); + spaces = await spacesClient.getAll({ purpose, includeAuthorizedPurposes }); + log.debug( + `Retrieved ${spaces.length} spaces for ${purpose} purpose with includeAuthorizedPurposes=${includeAuthorizedPurposes}` + ); + } catch (error) { + log.debug( + `Error retrieving spaces for ${purpose} purpose with includeAuthorizedPurposes=${includeAuthorizedPurposes}: ${error}` + ); + return response.customError(wrapError(error)); + } - return response.ok({ body: spaces }); - }) - ); + return response.ok({ body: spaces }); + }) + ); } diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts index 9b017839fb25c..984d684762159 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts @@ -16,9 +16,11 @@ import { httpServiceMock, loggingSystemMock, } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { initPostSpacesApi } from './post'; +import { API_VERSIONS } from '../../../../common'; import { spacesConfig } from '../../../lib/__fixtures__'; import { SpacesClientService } from '../../../spaces_client'; import { SpacesService } from '../../../spaces_service'; @@ -36,6 +38,7 @@ describe('Spaces Public API', () => { const setup = async () => { const httpService = httpServiceMock.createSetupContract(); const router = httpService.createRouter(); + const versionedRouterMock = router.versioned as MockedVersionedRouter; const coreStart = coreMock.createStart(); @@ -75,11 +78,13 @@ describe('Spaces Public API', () => { isServerless: false, }); - const [routeDefinition, routeHandler] = router.post.mock.calls[0]; + const { handler, config } = versionedRouterMock.getRoute('post', '/api/spaces/space').versions[ + API_VERSIONS.public.v1 + ]; return { - routeValidation: routeDefinition.validate as RouteValidatorConfig<{}, {}, {}>, - routeHandler, + routeValidation: (config.validate as any).request as RouteValidatorConfig<{}, {}, {}>, + routeHandler: handler, savedObjectsRepositoryMock, }; }; diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.ts b/x-pack/plugins/spaces/server/routes/api/external/post.ts index db3cc2c1cdcae..c47ea4ad5f9bf 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/post.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/post.ts @@ -10,6 +10,7 @@ import Boom from '@hapi/boom'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { ExternalRouteDeps } from '.'; +import { API_VERSIONS } from '../../../../common'; import { wrapError } from '../../../lib/errors'; import { getSpaceSchema } from '../../../lib/space_schema'; import { createLicensedRouteHandler } from '../../lib'; @@ -17,37 +18,42 @@ import { createLicensedRouteHandler } from '../../lib'; export function initPostSpacesApi(deps: ExternalRouteDeps) { const { router, log, getSpacesService, isServerless } = deps; - router.post( - { + router.versioned + .post({ path: '/api/spaces/space', + access: 'public', + description: `Create a space`, options: { - access: isServerless ? 'internal' : 'public', - description: `Create a space`, + tags: ['oas-tag:spaces'], }, - validate: { - body: getSpaceSchema(isServerless), + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + body: getSpaceSchema(isServerless), + }, + }, }, - }, - createLicensedRouteHandler(async (context, request, response) => { - log.debug(`Inside POST /api/spaces/space`); - const spacesClient = getSpacesService().createSpacesClient(request); - - const space = request.body; - - try { - log.debug(`Attempting to create space`); - const createdSpace = await spacesClient.create(space); - return response.ok({ body: createdSpace }); - } catch (error) { - if (SavedObjectsErrorHelpers.isConflictError(error)) { - const { body } = wrapError( - Boom.conflict(`A space with the identifier ${space.id} already exists.`) - ); - return response.conflict({ body }); + createLicensedRouteHandler(async (context, request, response) => { + log.debug(`Inside POST /api/spaces/space`); + const spacesClient = getSpacesService().createSpacesClient(request); + const space = request.body; + try { + log.debug(`Attempting to create space`); + const createdSpace = await spacesClient.create(space); + return response.ok({ body: createdSpace }); + } catch (error) { + if (SavedObjectsErrorHelpers.isConflictError(error)) { + const { body } = wrapError( + Boom.conflict(`A space with the identifier ${space.id} already exists.`) + ); + return response.conflict({ body }); + } + log.debug(`Error creating space: ${error}`); + return response.customError(wrapError(error)); } - log.debug(`Error creating space: ${error}`); - return response.customError(wrapError(error)); - } - }) - ); + }) + ); } diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts index fd255a8aadc2b..8aa71d30fc4bb 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts @@ -16,9 +16,11 @@ import { httpServiceMock, loggingSystemMock, } from '@kbn/core/server/mocks'; +import type { MockedVersionedRouter } from '@kbn/core-http-router-server-mocks'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { initPutSpacesApi } from './put'; +import { API_VERSIONS } from '../../../../common'; import { spacesConfig } from '../../../lib/__fixtures__'; import { SpacesClientService } from '../../../spaces_client'; import { SpacesService } from '../../../spaces_service'; @@ -36,6 +38,7 @@ describe('PUT /api/spaces/space', () => { const setup = async () => { const httpService = httpServiceMock.createSetupContract(); const router = httpService.createRouter(); + const versionedRouterMock = router.versioned as MockedVersionedRouter; const coreStart = coreMock.createStart(); @@ -75,11 +78,12 @@ describe('PUT /api/spaces/space', () => { isServerless: false, }); - const [routeDefinition, routeHandler] = router.put.mock.calls[0]; + const { handler, config } = versionedRouterMock.getRoute('put', '/api/spaces/space/{id}') + .versions[API_VERSIONS.public.v1]; return { - routeValidation: routeDefinition.validate as RouteValidatorConfig<{}, {}, {}>, - routeHandler, + routeValidation: (config.validate as any).request as RouteValidatorConfig<{}, {}, {}>, + routeHandler: handler, savedObjectsRepositoryMock, }; }; diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.ts b/x-pack/plugins/spaces/server/routes/api/external/put.ts index 4612aedb4c151..10374dc94f600 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/put.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/put.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { ExternalRouteDeps } from '.'; -import type { Space } from '../../../../common'; +import { API_VERSIONS, type Space } from '../../../../common'; import { wrapError } from '../../../lib/errors'; import { getSpaceSchema } from '../../../lib/space_schema'; import { createLicensedRouteHandler } from '../../lib'; @@ -17,37 +17,44 @@ import { createLicensedRouteHandler } from '../../lib'; export function initPutSpacesApi(deps: ExternalRouteDeps) { const { router, getSpacesService, isServerless } = deps; - router.put( - { + router.versioned + .put({ path: '/api/spaces/space/{id}', + access: 'public', + description: `Update a space`, options: { - access: isServerless ? 'internal' : 'public', - description: `Update a space`, + tags: ['oas-tag:spaces'], }, - validate: { - params: schema.object({ - id: schema.string(), - }), - body: getSpaceSchema(isServerless), + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + body: getSpaceSchema(isServerless), + }, + }, }, - }, - createLicensedRouteHandler(async (context, request, response) => { - const spacesClient = getSpacesService().createSpacesClient(request); + createLicensedRouteHandler(async (context, request, response) => { + const spacesClient = getSpacesService().createSpacesClient(request); - const space = request.body; - const id = request.params.id; + const space = request.body; + const id = request.params.id; - let result: Space; - try { - result = await spacesClient.update(id, { ...space }); - } catch (error) { - if (SavedObjectsErrorHelpers.isNotFoundError(error)) { - return response.notFound(); + let result: Space; + try { + result = await spacesClient.update(id, { ...space }); + } catch (error) { + if (SavedObjectsErrorHelpers.isNotFoundError(error)) { + return response.notFound(); + } + return response.customError(wrapError(error)); } - return response.customError(wrapError(error)); - } - return response.ok({ body: result }); - }) - ); + return response.ok({ body: result }); + }) + ); } diff --git a/x-pack/plugins/spaces/tsconfig.json b/x-pack/plugins/spaces/tsconfig.json index 20d3f7d3175d8..b669f97f0b8a1 100644 --- a/x-pack/plugins/spaces/tsconfig.json +++ b/x-pack/plugins/spaces/tsconfig.json @@ -38,7 +38,6 @@ "@kbn/security-plugin-types-public", "@kbn/cloud-plugin", "@kbn/core-analytics-browser", - "@kbn/core-analytics-browser", "@kbn/security-plugin-types-common", "@kbn/core-application-browser", "@kbn/unsaved-changes-prompt", @@ -52,6 +51,7 @@ "@kbn/core-notifications-browser", "@kbn/logging", "@kbn/core-logging-browser-mocks", + "@kbn/core-http-router-server-mocks" ], "exclude": [ "target/**/*", diff --git a/x-pack/test/common/services/spaces.ts b/x-pack/test/common/services/spaces.ts index a1657996239ac..98cc54e456200 100644 --- a/x-pack/test/common/services/spaces.ts +++ b/x-pack/test/common/services/spaces.ts @@ -44,7 +44,9 @@ export function SpacesServiceProvider({ getService }: FtrProviderContext) { : undefined; const axios = Axios.create({ - headers: { 'kbn-xsrf': 'x-pack/ftr/services/spaces/space' }, + headers: { + 'kbn-xsrf': 'x-pack/ftr/services/spaces/space', + }, baseURL: url, maxRedirects: 0, validateStatus: () => true, // we do our own validation below and throw better error messages diff --git a/x-pack/test_serverless/api_integration/test_suites/common/management/multiple_spaces_enabled.ts b/x-pack/test_serverless/api_integration/test_suites/common/management/multiple_spaces_enabled.ts index e5c4053b97e73..f90539f0cbfef 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/management/multiple_spaces_enabled.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/management/multiple_spaces_enabled.ts @@ -26,6 +26,8 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const svlCommonApi = getService('svlCommonApi'); const roleScopedSupertest = getService('roleScopedSupertest'); + const samlAuth = getService('samlAuth'); + // CRUD operations to become public APIs: https://github.com/elastic/kibana/issues/192153 let supertestAdminWithApiKey: SupertestWithRoleScopeType; let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType; @@ -48,7 +50,7 @@ export default function ({ getService }: FtrProviderContext) { describe('spaces', function () { before(async () => { supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin', { - withInternalHeaders: true, + withCommonHeaders: true, }); supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( 'admin', @@ -242,42 +244,166 @@ export default function ({ getService }: FtrProviderContext) { }); }); - // These tests just test access to API endpoints + // These tests just test access to API endpoints, in this case + // when accessed without internal headers they will return 400 // They will be included in deployment agnostic testing once spaces // are enabled in production. describe(`Access`, () => { - it('#copyToSpace', async () => { - const { body, status } = await supertestAdminWithApiKey.post( - '/api/spaces/_copy_saved_objects' - ); - svlCommonApi.assertResponseStatusCode(400, status, body); - }); - it('#resolveCopyToSpaceErrors', async () => { - const { body, status } = await supertestAdminWithApiKey.post( - '/api/spaces/_resolve_copy_saved_objects_errors' - ); - svlCommonApi.assertResponseStatusCode(400, status, body); - }); - it('#updateObjectsSpaces', async () => { - const { body, status } = await supertestAdminWithApiKey.post( - '/api/spaces/_update_objects_spaces' - ); - svlCommonApi.assertResponseStatusCode(400, status, body); - }); - it('#getShareableReferences', async () => { - const { body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_get_shareable_references') - .send({ - objects: [{ type: 'a', id: 'a' }], + describe(`internal`, () => { + it('#getActiveSpace requires internal header', async () => { + let body: any; + let status: number; + + ({ body, status } = await supertestAdminWithApiKey + .get('/internal/spaces/_active_space') + .set(samlAuth.getCommonRequestHeader())); + // expect a rejection because we're not using the internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining( + 'method [get] exists but is not available with the current configuration' + ), + }); + expect(status).toBe(400); + + ({ body, status } = await supertestAdminWithApiKey + .get('/internal/spaces/_active_space') + .set(samlAuth.getInternalRequestHeader())); + // expect success because we're using the internal header + expect(body).toEqual( + expect.objectContaining({ + id: 'default', + }) + ); + expect(status).toBe(200); + }); + + it('#copyToSpace requires internal header', async () => { + let body: any; + let status: number; + + ({ body, status } = await supertestAdminWithApiKey.post( + '/api/spaces/_copy_saved_objects' + )); + // expect a rejection because we're not using the internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining( + 'method [post] exists but is not available with the current configuration' + ), + }); + + ({ body, status } = await supertestAdminWithApiKey + .post('/api/spaces/_copy_saved_objects') + .set(samlAuth.getInternalRequestHeader())); + + svlCommonApi.assertResponseStatusCode(400, status, body); + + // expect 400 for missing body + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: '[request body]: expected a plain object value, but found [null] instead.', }); - svlCommonApi.assertResponseStatusCode(200, status, body); + }); + + it('#resolveCopyToSpaceErrors requires internal header', async () => { + let body: any; + let status: number; + + ({ body, status } = await supertestAdminWithApiKey.post( + '/api/spaces/_resolve_copy_saved_objects_errors' + )); + // expect a rejection because we're not using the internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining( + 'method [post] exists but is not available with the current configuration' + ), + }); + + ({ body, status } = await supertestAdminWithApiKey + .post('/api/spaces/_resolve_copy_saved_objects_errors') + .set(samlAuth.getInternalRequestHeader())); + + svlCommonApi.assertResponseStatusCode(400, status, body); + + // expect 400 for missing body + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: '[request body]: expected a plain object value, but found [null] instead.', + }); + }); + + it('#updateObjectsSpaces requires internal header', async () => { + let body: any; + let status: number; + + ({ body, status } = await supertestAdminWithApiKey.post( + '/api/spaces/_update_objects_spaces' + )); + // expect a rejection because we're not using the internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining( + 'method [post] exists but is not available with the current configuration' + ), + }); + + ({ body, status } = await supertestAdminWithApiKey + .post('/api/spaces/_update_objects_spaces') + .set(samlAuth.getInternalRequestHeader())); + + svlCommonApi.assertResponseStatusCode(400, status, body); + + // expect 400 for missing body + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: '[request body]: expected a plain object value, but found [null] instead.', + }); + }); + + it('#getShareableReferences requires internal header', async () => { + let body: any; + let status: number; + + ({ body, status } = await supertestAdminWithApiKey.post( + '/api/spaces/_get_shareable_references' + )); + // expect a rejection because we're not using the internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining( + 'method [post] exists but is not available with the current configuration' + ), + }); + + ({ body, status } = await supertestAdminWithApiKey + .post('/api/spaces/_get_shareable_references') + .set(samlAuth.getInternalRequestHeader()) + .send({ + objects: [{ type: 'a', id: 'a' }], + })); + + svlCommonApi.assertResponseStatusCode(200, status, body); + }); }); - it('#disableLegacyUrlAliases', async () => { - const { body, status } = await supertestAdminWithApiKey.post( - '/api/spaces/_disable_legacy_url_aliases' - ); - // without a request body we would normally a 400 bad request if the endpoint was registered - svlCommonApi.assertApiNotFound(body, status); + + describe(`disabled`, () => { + it('#disableLegacyUrlAliases', async () => { + const { body, status } = await supertestAdminWithApiKey.post( + '/api/spaces/_disable_legacy_url_aliases' + ); + // without a request body we would normally a 400 bad request if the endpoint was registered + svlCommonApi.assertApiNotFound(body, status); + }); }); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/management/spaces.ts b/x-pack/test_serverless/api_integration/test_suites/common/management/spaces.ts index 7014dcd6100df..4bde897e714af 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/management/spaces.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/management/spaces.ts @@ -19,7 +19,9 @@ export default function ({ getService }: FtrProviderContext) { describe('spaces', function () { before(async () => { // admin is the only predefined role that will work for all 3 solutions - supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin'); + supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin', { + withCommonHeaders: true, + }); supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( 'admin', { @@ -33,93 +35,82 @@ export default function ({ getService }: FtrProviderContext) { }); describe('route access', () => { - it('#delete', async () => { - const { body, status } = await supertestAdminWithApiKey - .delete('/api/spaces/space/default') - .set(samlAuth.getInternalRequestHeader()); - - svlCommonApi.assertResponseStatusCode(400, status, body); - }); - - // Skipped due to change in QA environment for role management and spaces - // TODO: revisit once the change is rolled out to all environments - it.skip('#create', async () => { - const { body, status } = await supertestAdminWithApiKey - .post('/api/spaces/space') - .set(samlAuth.getInternalRequestHeader()) - .send({ + describe('public (CRUD)', () => { + // Skipped due to change in QA environment for role management and spaces + // TODO: revisit once the change is rolled out to all environments + it.skip('#create', async () => { + const { body, status } = await supertestAdminWithApiKey.post('/api/spaces/space').send({ id: 'custom', name: 'Custom', disabledFeatures: [], }); - svlCommonApi.assertResponseStatusCode(400, status, body); - }); + svlCommonApi.assertResponseStatusCode(400, status, body); - it('#update requires internal header', async () => { - const { body, status } = await supertestAdminWithApiKey - .put('/api/spaces/space/default') - .set(samlAuth.getInternalRequestHeader()) - .send({ - id: 'default', - name: 'UPDATED!', - disabledFeatures: [], + // Should fail due to maximum spaces limit, not because of lacking internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: + 'Unable to create Space, this exceeds the maximum number of spaces set by the xpack.spaces.maxSpaces setting', }); + }); - svlCommonApi.assertResponseStatusCode(200, status, body); - }); - - it('#copyToSpace', async () => { - const { body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_copy_saved_objects') - .set(samlAuth.getInternalRequestHeader()); + it('#get', async () => { + const { body, status } = await supertestAdminWithApiKey.get('/api/spaces/space/default'); + // expect success because we're using the internal header + expect(body).toEqual(expect.objectContaining({ id: 'default' })); + expect(status).toBe(200); + }); - svlCommonApi.assertResponseStatusCode(400, status, body); - }); + it('#getAll', async () => { + const { body, status } = await supertestAdminWithApiKey.get('/api/spaces/space'); + // expect success because we're using the internal header + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 'default', + }), + ]) + ); + expect(status).toBe(200); + }); - it('#resolveCopyToSpaceErrors', async () => { - const { body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_resolve_copy_saved_objects_errors') - .set(samlAuth.getInternalRequestHeader()); + it('#update', async () => { + const { body, status } = await supertestAdminWithApiKey + .put('/api/spaces/space/default') + .send({ + id: 'default', + name: 'UPDATED!', + disabledFeatures: [], + }); - svlCommonApi.assertResponseStatusCode(400, status, body); - }); + svlCommonApi.assertResponseStatusCode(200, status, body); + }); - it('#updateObjectsSpaces', async () => { - const { body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_update_objects_spaces') - .set(samlAuth.getInternalRequestHeader()); + it('#delete', async () => { + const { body, status } = await supertestAdminWithApiKey.delete( + '/api/spaces/space/default' + ); - svlCommonApi.assertResponseStatusCode(400, status, body); - }); + svlCommonApi.assertResponseStatusCode(400, status, body); - it('#getShareableReferences', async () => { - const { body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_get_shareable_references') - .set(samlAuth.getInternalRequestHeader()) - .send({ - objects: [{ type: 'a', id: 'a' }], + // 400 with specific reason - cannot delete the default space + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: 'The default space cannot be deleted because it is reserved.', }); - - svlCommonApi.assertResponseStatusCode(200, status, body); - }); - - it('#disableLegacyUrlAliases', async () => { - const { body, status } = await supertestAdminWithApiKey - .post('/api/spaces/_disable_legacy_url_aliases') - .set(samlAuth.getInternalRequestHeader()); - - // without a request body we would normally a 400 bad request if the endpoint was registered - svlCommonApi.assertApiNotFound(body, status); + }); }); describe('internal', () => { - it('#get requires internal header', async () => { + it('#getActiveSpace requires internal header', async () => { let body: any; let status: number; - ({ body, status } = await supertestAdminWithApiKey - .get('/api/spaces/space/default') + ({ body, status } = await supertestAdminWithCookieCredentials + .get('/internal/spaces/_active_space') .set(samlAuth.getCommonRequestHeader())); // expect a rejection because we're not using the internal header expect(body).toEqual({ @@ -131,8 +122,8 @@ export default function ({ getService }: FtrProviderContext) { }); expect(status).toBe(400); - ({ body, status } = await supertestAdminWithApiKey - .get('/api/spaces/space/default') + ({ body, status } = await supertestAdminWithCookieCredentials + .get('/internal/spaces/_active_space') .set(samlAuth.getInternalRequestHeader())); // expect success because we're using the internal header expect(body).toEqual( @@ -143,64 +134,131 @@ export default function ({ getService }: FtrProviderContext) { expect(status).toBe(200); }); - it('#getAll requires internal header', async () => { + it('#copyToSpace requires internal header', async () => { let body: any; let status: number; + ({ body, status } = await supertestAdminWithApiKey.post( + '/api/spaces/_copy_saved_objects' + )); + // expect a rejection because we're not using the internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining( + 'method [post] exists but is not available with the current configuration' + ), + }); + ({ body, status } = await supertestAdminWithApiKey - .get('/api/spaces/space') - .set(samlAuth.getCommonRequestHeader())); + .post('/api/spaces/_copy_saved_objects') + .set(samlAuth.getInternalRequestHeader())); + + svlCommonApi.assertResponseStatusCode(400, status, body); + + // expect 400 for missing body + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: '[request body]: expected a plain object value, but found [null] instead.', + }); + }); + + it('#resolveCopyToSpaceErrors requires internal header', async () => { + let body: any; + let status: number; + + ({ body, status } = await supertestAdminWithApiKey.post( + '/api/spaces/_resolve_copy_saved_objects_errors' + )); // expect a rejection because we're not using the internal header expect(body).toEqual({ statusCode: 400, error: 'Bad Request', message: expect.stringContaining( - 'method [get] exists but is not available with the current configuration' + 'method [post] exists but is not available with the current configuration' ), }); - expect(status).toBe(400); ({ body, status } = await supertestAdminWithApiKey - .get('/api/spaces/space') + .post('/api/spaces/_resolve_copy_saved_objects_errors') .set(samlAuth.getInternalRequestHeader())); - // expect success because we're using the internal header - expect(body).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: 'default', - }), - ]) - ); - expect(status).toBe(200); + + svlCommonApi.assertResponseStatusCode(400, status, body); + + // expect 400 for missing body + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: '[request body]: expected a plain object value, but found [null] instead.', + }); }); - it('#getActiveSpace requires internal header', async () => { + it('#updateObjectsSpaces requires internal header', async () => { let body: any; let status: number; - ({ body, status } = await supertestAdminWithCookieCredentials - .get('/internal/spaces/_active_space') - .set(samlAuth.getCommonRequestHeader())); + ({ body, status } = await supertestAdminWithApiKey.post( + '/api/spaces/_update_objects_spaces' + )); // expect a rejection because we're not using the internal header expect(body).toEqual({ statusCode: 400, error: 'Bad Request', message: expect.stringContaining( - 'method [get] exists but is not available with the current configuration' + 'method [post] exists but is not available with the current configuration' ), }); - expect(status).toBe(400); - ({ body, status } = await supertestAdminWithCookieCredentials - .get('/internal/spaces/_active_space') + ({ body, status } = await supertestAdminWithApiKey + .post('/api/spaces/_update_objects_spaces') .set(samlAuth.getInternalRequestHeader())); - // expect success because we're using the internal header - expect(body).toEqual( - expect.objectContaining({ - id: 'default', - }) - ); - expect(status).toBe(200); + + svlCommonApi.assertResponseStatusCode(400, status, body); + + // expect 400 for missing body + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: '[request body]: expected a plain object value, but found [null] instead.', + }); + }); + + it('#getShareableReferences requires internal header', async () => { + let body: any; + let status: number; + + ({ body, status } = await supertestAdminWithApiKey.post( + '/api/spaces/_get_shareable_references' + )); + // expect a rejection because we're not using the internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining( + 'method [post] exists but is not available with the current configuration' + ), + }); + + ({ body, status } = await supertestAdminWithApiKey + .post('/api/spaces/_get_shareable_references') + .set(samlAuth.getInternalRequestHeader()) + .send({ + objects: [{ type: 'a', id: 'a' }], + })); + + svlCommonApi.assertResponseStatusCode(200, status, body); + }); + }); + + describe('disabled', () => { + it('#disableLegacyUrlAliases', async () => { + const { body, status } = await supertestAdminWithApiKey + .post('/api/spaces/_disable_legacy_url_aliases') + .set(samlAuth.getInternalRequestHeader()); + + // without a request body we would normally a 400 bad request if the endpoint was registered + svlCommonApi.assertApiNotFound(body, status); }); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts index 4c77607d7d844..bc01b14848eff 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts @@ -37,65 +37,66 @@ export default function ({ getService }: FtrProviderContext) { 'admin', { useCookieHeader: true, - withInternalHeaders: true, } ); supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin', { - withInternalHeaders: true, + withCommonHeaders: true, }); }); after(async () => { await supertestAdminWithApiKey.destroy(); }); describe('route access', () => { - describe('internal', () => { - describe('disabled', () => { - // Skipped due to change in QA environment for role management and spaces - // TODO: revisit once the change is rolled out to all environments - it.skip('get all privileges', async () => { - const { body, status } = await supertestAdminWithApiKey.get('/api/security/privileges'); - svlCommonApi.assertApiNotFound(body, status); - }); + describe('disabled', () => { + // Skipped due to change in QA environment for role management and spaces + // TODO: revisit once the change is rolled out to all environments + it.skip('get all privileges', async () => { + const { body, status } = await supertestAdminWithApiKey.get('/api/security/privileges'); + svlCommonApi.assertApiNotFound(body, status); + }); - // Skipped due to change in QA environment for role management and spaces - // TODO: revisit once the change is rolled out to all environments - it.skip('get built-in elasticsearch privileges', async () => { - const { body, status } = await supertestAdminWithCookieCredentials.get( - '/internal/security/esPrivileges/builtin' - ); - svlCommonApi.assertApiNotFound(body, status); - }); + // Skipped due to change in QA environment for role management and spaces + // TODO: revisit once the change is rolled out to all environments + it.skip('get built-in elasticsearch privileges', async () => { + const { body, status } = await supertestAdminWithCookieCredentials.get( + '/internal/security/esPrivileges/builtin' + ); + svlCommonApi.assertApiNotFound(body, status); + }); - it('create/update roleAuthc', async () => { - const { body, status } = await supertestAdminWithApiKey.put('/api/security/role/test'); - svlCommonApi.assertApiNotFound(body, status); - }); + // Role CRUD APIs are gated behind the xpack.security.roleManagementEnabled config + // setting. This setting is false by default on serverless. When the custom roles + // feature is enabled, this setting will be true, and the tests from + // roles_routes_feature_flag.ts can be moved here to replace these. + it('create/update roleAuthc', async () => { + const { body, status } = await supertestAdminWithApiKey.put('/api/security/role/test'); + svlCommonApi.assertApiNotFound(body, status); + }); - it('get roleAuthc', async () => { - const { body, status } = await supertestAdminWithApiKey.get( - '/api/security/role/superuser' - ); - svlCommonApi.assertApiNotFound(body, status); - }); + it('get role', async () => { + const { body, status } = await supertestAdminWithApiKey.get( + '/api/security/role/superuser' + ); + svlCommonApi.assertApiNotFound(body, status); + }); - it('get all roles', async () => { - const { body, status } = await supertestAdminWithApiKey.get('/api/security/role'); - svlCommonApi.assertApiNotFound(body, status); - }); + it('get all roles', async () => { + const { body, status } = await supertestAdminWithApiKey.get('/api/security/role'); + svlCommonApi.assertApiNotFound(body, status); + }); - it('delete roleAuthc', async () => { - const { body, status } = await supertestAdminWithApiKey.delete( - '/api/security/role/superuser' - ); - svlCommonApi.assertApiNotFound(body, status); - }); + it('delete role', async () => { + const { body, status } = await supertestAdminWithApiKey.delete( + '/api/security/role/superuser' + ); + svlCommonApi.assertApiNotFound(body, status); + }); - it('get shared saved object permissions', async () => { - const { body, status } = await supertestAdminWithCookieCredentials.get( - '/internal/security/_share_saved_object_permissions' - ); - svlCommonApi.assertApiNotFound(body, status); - }); + it('get shared saved object permissions', async () => { + const { body, status } = await supertestAdminWithCookieCredentials.get( + '/internal/security/_share_saved_object_permissions' + ); + svlCommonApi.assertApiNotFound(body, status); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/roles_routes_feature_flag.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/roles_routes_feature_flag.ts index da03ad14047b1..7f2237eda4b44 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/roles_routes_feature_flag.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/roles_routes_feature_flag.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import type { Role } from '@kbn/security-plugin-types-common'; import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -26,6 +26,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const platformSecurityUtils = getService('platformSecurityUtils'); const roleScopedSupertest = getService('roleScopedSupertest'); + const svlCommonApi = getService('svlCommonApi'); let supertestAdminWithApiKey: SupertestWithRoleScopeType; let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType; const es = getService('es'); @@ -41,7 +42,7 @@ export default function ({ getService }: FtrProviderContext) { } ); supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin', { - withInternalHeaders: true, + withCommonHeaders: true, }); }); after(async () => { @@ -86,7 +87,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(204); const role = await es.security.getRole({ name: 'role_with_privileges' }); - expect(role).to.eql({ + expect(role).toEqual({ role_with_privileges: { cluster: ['manage'], indices: [ @@ -425,7 +426,6 @@ export default function ({ getService }: FtrProviderContext) { .expect(200) .expect((res: { body: Role[] }) => { const roles = res.body; - expect(roles).to.be.an('array'); const success = roles.every((role) => { return ( @@ -440,8 +440,8 @@ export default function ({ getService }: FtrProviderContext) { const expectedRole = roles.find((role) => role.name === 'space_role_to_get'); - expect(success).to.be(true); - expect(expectedRole).to.be.an('object'); + expect(success).toBe(true); + expect(expectedRole).toBeTruthy(); }); }); }); @@ -508,7 +508,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(204); const role = await es.security.getRole({ name: 'role_to_update' }); - expect(role).to.eql({ + expect(role).toEqual({ role_to_update: { cluster: ['manage'], indices: [ @@ -582,9 +582,9 @@ export default function ({ getService }: FtrProviderContext) { const role = await es.security.getRole({ name: 'role_to_update_with_dls_fls' }); - expect(role.role_to_update_with_dls_fls.cluster).to.eql(['manage']); - expect(role.role_to_update_with_dls_fls.indices[0].names).to.eql(['logstash-*']); - expect(role.role_to_update_with_dls_fls.indices[0].query).to.eql( + expect(role.role_to_update_with_dls_fls.cluster).toEqual(['manage']); + expect(role.role_to_update_with_dls_fls.indices[0].names).toEqual(['logstash-*']); + expect(role.role_to_update_with_dls_fls.indices[0].query).toEqual( `{ "match": { "geo.src": "CN" } }` ); }); @@ -652,7 +652,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(400); const role = await es.security.getRole({ name: 'role_to_update' }); - expect(role).to.eql({ + expect(role).toEqual({ role_to_update: { cluster: ['monitor'], indices: [ @@ -753,7 +753,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(400); const role = await es.security.getRole({ name: 'role_to_update' }); - expect(role).to.eql({ + expect(role).toEqual({ role_to_update: { cluster: ['monitor'], indices: [ @@ -855,7 +855,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(400); const role = await es.security.getRole({ name: 'role_to_update' }); - expect(role).to.eql({ + expect(role).toEqual({ role_to_update: { cluster: ['monitor'], indices: [ @@ -924,7 +924,26 @@ export default function ({ getService }: FtrProviderContext) { { name: 'role_to_delete' }, { ignore: [404] } ); - expect(deletedRole).to.eql({}); + expect(deletedRole).toEqual({}); + }); + }); + + describe('Access', () => { + describe('public', () => { + it('reset session page', async () => { + const { status } = await supertestAdminWithCookieCredentials.get( + '/internal/security/reset_session_page.js' + ); + expect(status).toBe(200); + }); + }); + describe('Disabled', () => { + it('get shared saved object permissions', async () => { + const { body, status } = await supertestAdminWithCookieCredentials.get( + '/internal/security/_share_saved_object_permissions' + ); + svlCommonApi.assertApiNotFound(body, status); + }); }); }); });