diff --git a/src/security-server/admin-service/application/src/main/java/org/niis/xroad/securityserver/restapi/wsdl/OpenApiParser.java b/src/security-server/admin-service/application/src/main/java/org/niis/xroad/securityserver/restapi/wsdl/OpenApiParser.java index 3261b5187b..3cd6c4fad9 100644 --- a/src/security-server/admin-service/application/src/main/java/org/niis/xroad/securityserver/restapi/wsdl/OpenApiParser.java +++ b/src/security-server/admin-service/application/src/main/java/org/niis/xroad/securityserver/restapi/wsdl/OpenApiParser.java @@ -63,7 +63,8 @@ @Component public class OpenApiParser { - private static final String SUPPORTED_OPENAPI_MINOR_VERSION = "3.0"; + private static final String SUPPORTED_OPENAPI_MINOR_VERSION_3_0 = "3.0"; + private static final String SUPPORTED_OPENAPI_VERSION_3_1_0 = "3.1.0"; private static final int BUF_SIZE = 8192; private static final long MAX_DESCRIPTION_SIZE = 10 * 1024 * 1024; private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); @@ -72,11 +73,11 @@ public class OpenApiParser { * Parse openapi3 description * * @return OpenApiParser.Result - * @throws ParsingException if parsing cannot be done + * @throws ParsingException if parsing cannot be done * @throws UnsupportedOpenApiVersionException if the openapi version is not supported */ public Result parse(String urlString) throws ParsingException, UnsupportedOpenApiVersionException { - URI openApiUrl = null; + URI openApiUrl; try { openApiUrl = new URI(urlString); } catch (URISyntaxException e) { @@ -127,13 +128,18 @@ public Result parse(String urlString) throws ParsingException, UnsupportedOpenAp private void verifyOpenApiVersion(JsonNode node) throws UnsupportedOpenApiVersionException { final String openapiVersion = node.get("openapi").asText(); - if (openapiVersion != null && !openapiVersion.startsWith(SUPPORTED_OPENAPI_MINOR_VERSION)) { + if (openapiVersion != null && !versionSupported(openapiVersion)) { String errorMsg = String.format("OpenAPI version %s not supported", openapiVersion); throw new UnsupportedOpenApiVersionException(errorMsg, new ErrorDeviation(ERROR_UNSUPPORTED_OPENAPI_VERSION)); } } + private boolean versionSupported(String version) { + return version.startsWith(SUPPORTED_OPENAPI_MINOR_VERSION_3_0) + || version.equals(SUPPORTED_OPENAPI_VERSION_3_1_0); + } + private void validate(SwaggerParseResult result, URI openApiUrl) throws ParsingException { if (result == null || result.getOpenAPI() == null) { throw new ParsingException("Unable to parse OpenAPI description from " + openApiUrl); diff --git a/src/security-server/admin-service/application/src/test/java/org/niis/xroad/securityserver/restapi/wsdl/OpenApiParserTest.java b/src/security-server/admin-service/application/src/test/java/org/niis/xroad/securityserver/restapi/wsdl/OpenApiParserTest.java index 809b85354a..5fd062bc32 100644 --- a/src/security-server/admin-service/application/src/test/java/org/niis/xroad/securityserver/restapi/wsdl/OpenApiParserTest.java +++ b/src/security-server/admin-service/application/src/test/java/org/niis/xroad/securityserver/restapi/wsdl/OpenApiParserTest.java @@ -30,6 +30,9 @@ import java.net.URL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + /** * Unit tests for OpenAPIParser */ @@ -39,8 +42,17 @@ public class OpenApiParserTest { public void shouldParseOpenApiYaml() throws OpenApiParser.ParsingException, UnsupportedOpenApiVersionException { URL url = getClass().getResource("/openapiparser/valid.yaml"); final OpenApiParser.Result result = new TestOpenApiParser().parse(url.toString()); - Assert.assertFalse(result.hasWarnings()); - Assert.assertEquals("https://example.org/api", result.getBaseUrl()); + assertFalse(result.hasWarnings()); + assertEquals("https://example.org/api", result.getBaseUrl()); + } + + @Test + public void shouldParseOpenApi31Yaml() throws OpenApiParser.ParsingException, UnsupportedOpenApiVersionException { + URL url = getClass().getResource("/openapiparser/v310.yaml"); + final OpenApiParser.Result result = new TestOpenApiParser().parse(url.toString()); + assertFalse(result.hasWarnings()); + assertEquals("https://example.org/api", result.getBaseUrl()); + assertEquals(3, result.getOperations().size()); } @Test @@ -48,15 +60,24 @@ public void shouldHaveWarnings() throws OpenApiParser.ParsingException, Unsuppor URL url = getClass().getResource("/openapiparser/warnings.yml"); final OpenApiParser.Result result = new TestOpenApiParser().parse(url.toString()); Assert.assertTrue(result.hasWarnings()); - Assert.assertEquals("https://{securityserver}/r1", result.getBaseUrl()); + assertEquals("https://{securityserver}/r1", result.getBaseUrl()); } @Test public void shouldParseOpenApiJson() throws OpenApiParser.ParsingException, UnsupportedOpenApiVersionException { URL url = getClass().getResource("/openapiparser/valid.json"); final OpenApiParser.Result result = new TestOpenApiParser().parse(url.toString()); - Assert.assertFalse(result.hasWarnings()); - Assert.assertEquals("https://example.org/api", result.getBaseUrl()); + assertFalse(result.hasWarnings()); + assertEquals("https://example.org/api", result.getBaseUrl()); + } + + @Test + public void shouldParseOpenApi31Json() throws OpenApiParser.ParsingException, UnsupportedOpenApiVersionException { + URL url = getClass().getResource("/openapiparser/v310.json"); + final OpenApiParser.Result result = new TestOpenApiParser().parse(url.toString()); + assertFalse(result.hasWarnings()); + assertEquals("https://example.org/api", result.getBaseUrl()); + assertEquals(3, result.getOperations().size()); } @Test(expected = OpenApiParser.ParsingException.class) @@ -76,14 +97,14 @@ public void shouldFailIfDuplicateEndpoint() throws OpenApiParser.ParsingExceptio @Test(expected = UnsupportedOpenApiVersionException.class) public void shouldFailOnUnsupportedOpenApiVersionYaml() throws OpenApiParser.ParsingException, UnsupportedOpenApiVersionException { - URL url = getClass().getResource("/openapiparser/v310.yaml"); + URL url = getClass().getResource("/openapiparser/invalid_version.yaml"); new TestOpenApiParser().parse(url.toString()); } @Test(expected = UnsupportedOpenApiVersionException.class) public void shouldFailOnUnsupportedOpenApiVersionJson() throws OpenApiParser.ParsingException, UnsupportedOpenApiVersionException { - URL url = getClass().getResource("/openapiparser/v310.json"); + URL url = getClass().getResource("/openapiparser/invalid_version.json"); new TestOpenApiParser().parse(url.toString()); } diff --git a/src/security-server/admin-service/application/src/test/resources/openapiparser/invalid_version.json b/src/security-server/admin-service/application/src/test/resources/openapiparser/invalid_version.json new file mode 100644 index 0000000000..c61c181ed0 --- /dev/null +++ b/src/security-server/admin-service/application/src/test/resources/openapiparser/invalid_version.json @@ -0,0 +1,70 @@ +{ + "openapi": "3.2.0", + "info": { + "version": "1.0", + "title": "Test" + }, + "servers": [ + { + "url": "https://example.org/api", + "description": "test" + } + ], + "paths": { + "/test": { + "post": { + "requestBody": { + "content": { + "text/plain": { + "schema": { + "exclusiveMaximum": 50, + "exclusiveMinimum": 1.22, + "type": "number", + "contentEncoding": "double", + "contentMediaType": "text/plain" + }, + "example": 3 + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + }, + "summary": "post" + }, + "get": { + "responses": { + "200": { + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + }, + "description": "OK" + } + }, + "summary": "get" + } + }, + "/file": { + "post": { + "summary": "file upload", + "requestBody": { + "content": { + "application/octet-stream": {} + } + }, + "responses": { + "201": { + "description": "OK" + } + } + } + } + } +} diff --git a/src/security-server/admin-service/application/src/test/resources/openapiparser/invalid_version.yaml b/src/security-server/admin-service/application/src/test/resources/openapiparser/invalid_version.yaml new file mode 100644 index 0000000000..9781126894 --- /dev/null +++ b/src/security-server/admin-service/application/src/test/resources/openapiparser/invalid_version.yaml @@ -0,0 +1,44 @@ +--- +openapi: 3.1.1 +servers: + - description: test + url: https://example.org/api +info: + title: Test + version: "1.0" +paths: + /test: + get: + summary: get + responses: + '200': + description: OK + content: + text/plain: + schema: + type: string + post: + summary: post + requestBody: + content: + text/plain: + schema: + exclusiveMaximum: 50 + exclusiveMinimum: 1.22 + type: number + contentEncoding: double + contentMediaType: text/plain + example: 3 + required: true + responses: + '200': + description: OK + /file: + post: + summary: file upload + requestBody: + content: + application/octet-stream: { } + responses: + '201': + description: OK diff --git a/src/security-server/admin-service/application/src/test/resources/openapiparser/v310.json b/src/security-server/admin-service/application/src/test/resources/openapiparser/v310.json index af201b7af5..af6683d1a2 100644 --- a/src/security-server/admin-service/application/src/test/resources/openapiparser/v310.json +++ b/src/security-server/admin-service/application/src/test/resources/openapiparser/v310.json @@ -1,7 +1,7 @@ { "openapi": "3.1.0", "info": { - "version": 1.0, + "version": "1.0", "title": "Test" }, "servers": [ @@ -17,10 +17,16 @@ "content": { "text/plain": { "schema": { - "type": "string" - } + "exclusiveMaximum": 50, + "exclusiveMinimum": 1.22, + "type": "number", + "contentEncoding": "double", + "contentMediaType": "text/plain" + }, + "example": 3 } - } + }, + "required": true }, "responses": { "200": { @@ -43,21 +49,21 @@ } }, "summary": "get" - }, - "get": { + } + }, + "/file": { + "post": { + "summary": "file upload", + "requestBody": { + "content": { + "application/octet-stream": {} + } + }, "responses": { - "200": { - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - }, + "201": { "description": "OK" } - }, - "summary": "get" + } } } } diff --git a/src/security-server/admin-service/application/src/test/resources/openapiparser/v310.yaml b/src/security-server/admin-service/application/src/test/resources/openapiparser/v310.yaml index 8667f9076a..1ab7afae4e 100644 --- a/src/security-server/admin-service/application/src/test/resources/openapiparser/v310.yaml +++ b/src/security-server/admin-service/application/src/test/resources/openapiparser/v310.yaml @@ -5,7 +5,7 @@ servers: url: https://example.org/api info: title: Test - version: 1.0 + version: "1.0" paths: /test: get: @@ -23,7 +23,22 @@ paths: content: text/plain: schema: - type: string + exclusiveMaximum: 50 + exclusiveMinimum: 1.22 + type: number + contentEncoding: double + contentMediaType: text/plain + example: 3 + required: true responses: '200': description: OK + /file: + post: + summary: file upload + requestBody: + content: + application/octet-stream: { } + responses: + '201': + description: OK diff --git a/src/security-server/admin-service/ui/src/locales/en.json b/src/security-server/admin-service/ui/src/locales/en.json index 41e415b46f..99ed4475ae 100644 --- a/src/security-server/admin-service/ui/src/locales/en.json +++ b/src/security-server/admin-service/ui/src/locales/en.json @@ -494,7 +494,7 @@ "timestamping_service_not_found": "Timestamping service not found", "token_not_active": "Token is not active", "token_not_found": "Token not found", - "unsupported_openapi_version": "Unsupported OpenAPI version. Only versions 3.0.x are currently supported.", + "unsupported_openapi_version": "Unsupported OpenAPI version. Only versions 3.0.x and 3.1.0 are currently supported.", "url_already_exists": "URL already exists", "validation_failure": "Validation failure", "warnings_detected": "Warnings detected", diff --git a/src/security-server/system-test/src/intTest/resources/behavior/01-ui/0540-ss-client-openapi-services.feature b/src/security-server/system-test/src/intTest/resources/behavior/01-ui/0540-ss-client-openapi-services.feature index 2cd7250474..946d6d6541 100644 --- a/src/security-server/system-test/src/intTest/resources/behavior/01-ui/0540-ss-client-openapi-services.feature +++ b/src/security-server/system-test/src/intTest/resources/behavior/01-ui/0540-ss-client-openapi-services.feature @@ -132,3 +132,23 @@ Feature: 0540 - SS: Client OpenApi REST services And Services sub-tab is selected When Service "OPENAPI3 (http://mock-server:1080/test-services/testopenapi11.yaml)" is deleted Then Service "OPENAPI3 (http://mock-server:1080/test-services/testopenapi11.yaml)" is missing in the list + + Scenario: Client service with openApi 3.1 json is added + Given Client "TestService" is opened + And Services sub-tab is selected + When Rest service dialog is opened and OpenApi spec is set to "http://mock-server:1080/test-services/testopenapi_v310.json" and service code "testOas31" + Then Dialog data is saved and success message "OpenApi3 service added" is shown + And Service "OPENAPI3 (http://mock-server:1080/test-services/testopenapi_v310.json)" is present in the list + When Service "OPENAPI3 (http://mock-server:1080/test-services/testopenapi_v310.json)" is expanded + And Service with code "testOas31" is opened + Then Service URL is "https://example.org/api", timeout is 60 and tls certificate verification is checked + When Service endpoints view is opened + Then Service endpoint with HTTP request method "GET" and path "/test" is present in the list + Then Service endpoint with HTTP request method "POST" and path "/test" is present in the list + Then Service endpoint with HTTP request method "POST" and path "/file" is present in the list + + Scenario: Adding service with invalid openApi version fails + Given Client "TestService" is opened + And Services sub-tab is selected + When Rest service dialog is opened and OpenApi spec is set to "http://mock-server:1080/test-services/testopenapi_invalid_version.yaml" and service code "testOas31x" + Then Dialog data is saved and error message "Unsupported OpenAPI version. Only versions 3.0.x and 3.1.0 are currently supported." is shown diff --git a/src/security-server/system-test/src/intTest/resources/behavior/01-ui/0570-ss-client-service-clients.feature b/src/security-server/system-test/src/intTest/resources/behavior/01-ui/0570-ss-client-service-clients.feature index fa38f984d9..16a9fa434e 100644 --- a/src/security-server/system-test/src/intTest/resources/behavior/01-ui/0570-ss-client-service-clients.feature +++ b/src/security-server/system-test/src/intTest/resources/behavior/01-ui/0570-ss-client-service-clients.feature @@ -20,11 +20,11 @@ Feature: 0570 - SS: Client Service clients When Service clients add subject wizard is opened And Service clients wizard is filtered to "Test" with 5 results and subject "CS:ORG:2908758-4:Management" is selected And Service clients subject "CS:GOV:0245437-2:test-consumer" is not selectable - And Service clients wizard services step is filtered to "" with 4 results and service "s3c2" is selected + And Service clients wizard services step is filtered to "" with 5 results and service "s3c2" is selected And Service clients add subject wizard is opened And Service clients wizard is filtered to "own" with 1 results and subject "CS:security-server-owners" is selected - And Service clients wizard services step is filtered to "" with 4 results and service "testOp1" is selected + And Service clients wizard services step is filtered to "" with 5 results and service "testOp1" is selected Then Service clients list is as follows | $memberName | $id | | Security Server owners | CS:security-server-owners | diff --git a/src/security-server/system-test/src/intTest/resources/nginx-container-files/var/lib/xroad/services/test-services/testopenapi_invalid_version.yaml b/src/security-server/system-test/src/intTest/resources/nginx-container-files/var/lib/xroad/services/test-services/testopenapi_invalid_version.yaml new file mode 100644 index 0000000000..9781126894 --- /dev/null +++ b/src/security-server/system-test/src/intTest/resources/nginx-container-files/var/lib/xroad/services/test-services/testopenapi_invalid_version.yaml @@ -0,0 +1,44 @@ +--- +openapi: 3.1.1 +servers: + - description: test + url: https://example.org/api +info: + title: Test + version: "1.0" +paths: + /test: + get: + summary: get + responses: + '200': + description: OK + content: + text/plain: + schema: + type: string + post: + summary: post + requestBody: + content: + text/plain: + schema: + exclusiveMaximum: 50 + exclusiveMinimum: 1.22 + type: number + contentEncoding: double + contentMediaType: text/plain + example: 3 + required: true + responses: + '200': + description: OK + /file: + post: + summary: file upload + requestBody: + content: + application/octet-stream: { } + responses: + '201': + description: OK diff --git a/src/security-server/system-test/src/intTest/resources/nginx-container-files/var/lib/xroad/services/test-services/testopenapi_v310.json b/src/security-server/system-test/src/intTest/resources/nginx-container-files/var/lib/xroad/services/test-services/testopenapi_v310.json new file mode 100644 index 0000000000..af6683d1a2 --- /dev/null +++ b/src/security-server/system-test/src/intTest/resources/nginx-container-files/var/lib/xroad/services/test-services/testopenapi_v310.json @@ -0,0 +1,70 @@ +{ + "openapi": "3.1.0", + "info": { + "version": "1.0", + "title": "Test" + }, + "servers": [ + { + "url": "https://example.org/api", + "description": "test" + } + ], + "paths": { + "/test": { + "post": { + "requestBody": { + "content": { + "text/plain": { + "schema": { + "exclusiveMaximum": 50, + "exclusiveMinimum": 1.22, + "type": "number", + "contentEncoding": "double", + "contentMediaType": "text/plain" + }, + "example": 3 + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + }, + "summary": "post" + }, + "get": { + "responses": { + "200": { + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + }, + "description": "OK" + } + }, + "summary": "get" + } + }, + "/file": { + "post": { + "summary": "file upload", + "requestBody": { + "content": { + "application/octet-stream": {} + } + }, + "responses": { + "201": { + "description": "OK" + } + } + } + } + } +}