├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── NOTICE ├── README.md ├── access-control ├── pom.xml └── src │ ├── main │ ├── java │ │ ├── com │ │ │ └── networknt │ │ │ │ └── openapi │ │ │ │ ├── AccessControlConfig.java │ │ │ │ └── AccessControlHandler.java │ │ └── module-info.j │ └── resources │ │ └── config │ │ ├── access-control-schema.json │ │ ├── access-control.yaml │ │ └── access-control.yml │ └── test │ ├── java │ └── com │ │ └── networknt │ │ └── openapi │ │ ├── AccessControlConfigTest.java │ │ └── AccessControlHandlerTest.java │ └── resources │ ├── logback-test.xml │ └── server.yml ├── openapi-config ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── networknt │ │ │ └── openapi │ │ │ ├── DefaultInjectableSpecValidator.java │ │ │ ├── InjectableSpecValidator.java │ │ │ └── OpenApiHandlerConfig.java │ └── resources │ │ └── config │ │ ├── openapi-handler-schema.json │ │ ├── openapi-handler.yaml │ │ ├── openapi-handler.yml │ │ └── openapi-inject.yml │ └── test │ ├── java │ └── com │ │ └── networknt │ │ └── openapi │ │ ├── DefaultInjectableSpecValidatorTest.java │ │ └── OpenApiHandlerConfigTest.java │ └── resources │ ├── config │ ├── client.truststore │ ├── handler.yml │ ├── openapi-handler-multiple.yml │ ├── openapi-inject-test-dup-method.yml │ ├── openapi-inject-test-dup-path.yml │ ├── openapi-inject-test-neg.yml │ ├── openapi-inject-test-pos.yml │ ├── openapi-market.yaml │ ├── openapi-petstore.yaml │ ├── openapi.json │ ├── openapi.yaml │ └── values.yml │ └── logback-test.xml ├── openapi-meta ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── networknt │ │ └── openapi │ │ ├── OpenApiEndpointSource.java │ │ ├── OpenApiHandler.java │ │ └── parameter │ │ ├── CookieHelper.java │ │ ├── CookieParameterDeserializer.java │ │ ├── Delimiters.java │ │ ├── HeaderParameterDeserializer.java │ │ ├── ParameterDeserializer.java │ │ ├── ParameterType.java │ │ ├── PathParameterDeserializer.java │ │ ├── PathParameterStyle.java │ │ ├── QueryParameterDeserializer.java │ │ ├── QueryParameterStyle.java │ │ ├── StyleParameterDeserializer.java │ │ └── ValueType.java │ └── test │ ├── java │ └── com │ │ └── networknt │ │ └── openapi │ │ ├── OpenApiEndpointSourceTest.java │ │ ├── OpenApiHandlerMultipleSpecsTest.java │ │ ├── OpenApiHandlerTest.java │ │ └── parameter │ │ ├── IntegrationTest.java │ │ ├── ParameterDeserializerTest.java │ │ ├── ParameterHandler.java │ │ ├── PathParameterDeserializerTest.java │ │ ├── PoJoParameter.java │ │ ├── PojoSchema.java │ │ └── QueryParameterDeserializerTest.java │ └── resources │ ├── config │ ├── client.truststore │ ├── handler.yml │ ├── openapi-handler-multiple.yml │ ├── openapi-inject-test-dup-method.yml │ ├── openapi-inject-test-dup-path.yml │ ├── openapi-inject-test-neg.yml │ ├── openapi-inject-test-pos.yml │ ├── openapi-market.yaml │ ├── openapi-petstore.yaml │ ├── openapi.json │ ├── openapi.yaml │ └── values.yml │ └── logback-test.xml ├── openapi-security ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── networknt │ │ │ └── openapi │ │ │ ├── JwtVerifyHandler.java │ │ │ ├── SimpleJwtVerifyHandler.java │ │ │ └── SwtVerifyHandler.java │ └── resources │ │ └── config │ │ ├── primary.crt │ │ └── secondary.crt │ └── test │ ├── java │ └── com │ │ └── networknt │ │ └── openapi │ │ ├── JwtVerifierHandlerMultipleSpecsTest.java │ │ ├── JwtVerifyHandlerTest.java │ │ ├── SwtVerifyHandlerTest.java │ │ ├── TestServer.java │ │ └── UnifiedSecurityHandlerTest.java │ └── resources │ ├── config │ ├── client.truststore │ ├── handler.yml │ ├── openapi-handler-multiple.yml │ ├── openapi-market.yaml │ ├── openapi-petstore.yaml │ ├── openapi.yaml │ ├── unified-security.yml │ └── values.yml │ └── logback-test.xml ├── openapi-validator ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── networknt │ │ │ └── openapi │ │ │ ├── RequestValidator.java │ │ │ ├── ResponseValidator.java │ │ │ ├── SchemaValidator.java │ │ │ └── ValidatorHandler.java │ └── resources │ │ └── messages.properties │ └── test │ ├── java │ └── com │ │ └── networknt │ │ └── openapi │ │ ├── ForwardRequestHandler.java │ │ ├── ParameterHandler.java │ │ ├── RequestValidatorTest.java │ │ ├── ResponseValidatorTest.java │ │ ├── ValidatorHandlerEnumTest.java │ │ ├── ValidatorHandlerMultipleSpecsTest.java │ │ └── ValidatorHandlerTest.java │ └── resources │ ├── config │ ├── client.truststore │ ├── handler.yml │ ├── openapi-bak.yaml │ ├── openapi-enum.yaml │ ├── openapi-handler-enum.yml │ ├── openapi-handler-multiple.yml │ ├── openapi-market.yaml │ ├── openapi-petstore.yaml │ ├── openapi-validator.yml │ ├── openapi.yaml │ └── responses.yml │ └── logback-test.xml ├── pom.xml ├── specification ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── networknt │ │ │ └── specification │ │ │ ├── FaviconHandler.java │ │ │ ├── SpecDisplayHandler.java │ │ │ ├── SpecSwaggerUIHandler.java │ │ │ └── SpecificationConfig.java │ └── resources │ │ └── config │ │ ├── favicon.ico │ │ ├── specification-schema.json │ │ ├── specification.yaml │ │ └── specification.yml │ └── test │ ├── java │ └── com │ │ └── networknt │ │ └── specification │ │ ├── SpecDisplayHandlerTest.java │ │ └── SpecSwaggerUIHandlerTest.java │ └── resources │ ├── config │ ├── client.truststore │ └── openapi.yaml │ └── logback-test.xml └── validator-config ├── pom.xml └── src └── main ├── java └── com │ └── networknt │ └── openapi │ └── ValidatorConfig.java └── resources └── config ├── openapi-validator-schema.json ├── openapi-validator.yaml └── openapi-validator.yml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | bower_components/ 3 | node_modules/ 4 | dist/ 5 | .idea/ 6 | .tmp/ 7 | .project 8 | .classpath 9 | .settings 10 | .metadata/ 11 | *.iml 12 | *.log 13 | *.tmp 14 | *.zip 15 | *.bak 16 | *.versionsBackup 17 | 18 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 19 | hs_err_pid* 20 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: check-added-large-files 9 | - repo: https://github.com/networknt/pre-commit-hook-keyword 10 | rev: f17c4de14fc24420f6768c19cad06ba03af06d86 11 | hooks: 12 | - id: keywordscan 13 | args: ["--keywords=c3VubGlmZQ==,Y2liYw==,c3VuIGxpZmU="] 14 | types: ["text"] 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | cache: 3 | directories: 4 | - $HOME/.m2 5 | jdk: 6 | - openjdk11 7 | branches: 8 | only: 9 | - master 10 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Light-rest-4j 2 | ============= 3 | 4 | 5 | Copyright (c) 2019 Network New Technologies Inc. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | 19 | ========================================================================== 20 | Light-4j Dependency 21 | ========================================================================== 22 | 23 | This project depends on code from light-4j and its dependencies. 24 | 25 | https://github.com/networknt/light-4j/blob/master/NOTICE 26 | 27 | ========================================================================== 28 | Third Party Dependencies 29 | ========================================================================== 30 | 31 | This project includes or depends on code from third party projects. 32 | 33 | The following are attribution notices from dependencies: 34 | 35 | ----------------- 36 | Commons-text 37 | ----------------- 38 | 39 | Licensed to the Apache Software Foundation (ASF) under one or more 40 | contributor license agreements. See the NOTICE file distributed with 41 | this work for additional information regarding copyright ownership. 42 | The ASF licenses this file to You under the Apache License, Version 2.0 43 | (the "License"); you may not use this file except in compliance with 44 | the License. You may obtain a copy of the License at 45 | 46 | http://www.apache.org/licenses/LICENSE-2.0 47 | 48 | Unless required by applicable law or agreed to in writing, software 49 | distributed under the License is distributed on an "AS IS" BASIS, 50 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 51 | See the License for the specific language governing permissions and 52 | limitations under the License. 53 | 54 | 55 | ----------------- 56 | Swagger Core 57 | ----------------- 58 | 59 | Copyright 2018 SmartBear Software 60 | 61 | Licensed under the Apache License, Version 2.0 (the "License"); 62 | you may not use this file except in compliance with the License. 63 | You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 64 | 65 | Unless required by applicable law or agreed to in writing, software 66 | distributed under the License is distributed on an "AS IS" BASIS, 67 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 68 | See the License for the specific language governing permissions and 69 | limitations under the License. 70 | 71 | 72 | ----------------- 73 | Swagger Parser 74 | ----------------- 75 | 76 | Copyright 2017 SmartBear Software 77 | 78 | Licensed under the Apache License, Version 2.0 (the "License"); 79 | you may not use this file except in compliance with the License. 80 | You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 81 | 82 | Unless required by applicable law or agreed to in writing, software 83 | distributed under the License is distributed on an "AS IS" BASIS, 84 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 85 | See the License for the specific language governing permissions and 86 | limitations under the License. 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A RESTful API or service framework built on top of light-4j 2 | 3 | [Stack Overflow](https://stackoverflow.com/questions/tagged/light-4j) | 4 | [Google Group](https://groups.google.com/forum/#!forum/light-4j) | 5 | [Gitter Chat](https://gitter.im/networknt/light-rest-4j) | 6 | [Subreddit](https://www.reddit.com/r/lightapi/) | 7 | [Youtube Channel](https://www.youtube.com/channel/UCHCRMWJVXw8iB7zKxF55Byw) | 8 | [Documentation](https://doc.networknt.com/style/light-rest-4j/) | 9 | [Contribution Guide](https://doc.networknt.com/contribute/) | 10 | 11 | [![Build Status](https://travis-ci.org/networknt/light-rest-4j.svg?branch=master)](https://travis-ci.org/networknt/light-rest-4j) [![codecov.io](https://codecov.io/github/networknt/light-rest-4j/coverage.svg?branch=master)](https://codecov.io/github/networknt/light-rest-4j?branch=master) 12 | 13 | This repository contains middleware handlers that work with either Swagger 2.0 or OpenAPI 3.0 specification. If you are starting a brand new project, it is highly recommended using the OpenAPI 3.0 specification for your design. 14 | 15 | To get started your first project, please visit the [getting started](https://doc.networknt.com/getting-started/light-rest-4j/) to walk through the process. 16 | 17 | For documentation on the handlers, please visit the [reference document](https://doc.networknt.com/style/light-rest-4j/). 18 | 19 | There are also numeric [tutorials](https://doc.networknt.com/tutorial/rest/) to show users how to build Restful services. 20 | -------------------------------------------------------------------------------- /access-control/pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 4.0.0 20 | 21 | 22 | com.networknt 23 | light-rest-4j 24 | 2.2.3-SNAPSHOT 25 | ../pom.xml 26 | 27 | 28 | access-control 29 | jar 30 | access-control 31 | A module that contains fine-grained authorization handlers to address business domain security 32 | 33 | 34 | 35 | com.networknt 36 | config 37 | 38 | 39 | com.networknt 40 | handler 41 | 42 | 43 | com.networknt 44 | status 45 | 46 | 47 | com.networknt 48 | openapi-parser 49 | 50 | 51 | com.networknt 52 | rule-loader 53 | 54 | 55 | com.networknt 56 | yaml-rule 57 | 58 | 59 | 60 | io.undertow 61 | undertow-core 62 | 63 | 64 | com.fasterxml.jackson.core 65 | jackson-databind 66 | 67 | 68 | org.slf4j 69 | slf4j-api 70 | 71 | 72 | 73 | ch.qos.logback 74 | logback-classic 75 | test 76 | 77 | 78 | junit 79 | junit 80 | test 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /access-control/src/main/java/module-info.j: -------------------------------------------------------------------------------- 1 | module com.networknt.openapi-control { 2 | exports com.networknt.openapi; 3 | 4 | requires com.networknt.config; 5 | requires com.networknt.handler; 6 | requires com.networknt.utility; 7 | 8 | requires com.fasterxml.jackson.core; 9 | requires org.slf4j; 10 | requires java.logging; 11 | } 12 | -------------------------------------------------------------------------------- /access-control/src/main/resources/config/access-control-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema" : "http://json-schema.org/draft-07/schema#", 3 | "type" : "object", 4 | "required" : [ "enabled", "accessRuleLogic", "defaultDeny", "skipPathPrefixes" ], 5 | "properties" : { 6 | "enabled" : { 7 | "type" : "boolean", 8 | "description" : "Enable Access Control Handler", 9 | "default" : true 10 | }, 11 | "accessRuleLogic" : { 12 | "type" : "string", 13 | "description" : "If there are multiple rules, the logic to combine them can be any or all. The default is any, and it\nmeans that any rule is satisfied, the access is granted. If all is set, all rules must be satisfied.", 14 | "default" : "any" 15 | }, 16 | "defaultDeny" : { 17 | "type" : "boolean", 18 | "description" : "If there is no access rule defined for the endpoint, default access is denied. Users can overwrite\nthis default action by setting this config value to false. If true, the handle will force users to\ndefine the rules for each endpoint when the access control handler is enabled.", 19 | "default" : true 20 | }, 21 | "skipPathPrefixes" : { 22 | "type" : "array", 23 | "description" : "Define a list of path prefixes to skip the access-control to ease the configuration for the handler.yml\nso that users can define some endpoint without fine-grained access-control security even through it uses\nthe default chain. This is useful if some endpoints want to skip the fine-grained access control in the\napplication. The format is a list of strings separated with commas or a JSON list in values.yml definition\nfrom config server, or you can use yaml format in externalized access-control.yml file.", 24 | "items" : { 25 | "type" : "string" 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /access-control/src/main/resources/config/access-control.yaml: -------------------------------------------------------------------------------- 1 | # AccessControlHandler will be the last middleware handler before the proxy on the sidecar or the last 2 | # one before the business handler to handle the fine-grained authorization in the business domain. 3 | # Enable Access Control Handler 4 | enabled: ${access-control.enabled:true} 5 | # If there are multiple rules, the logic to combine them can be any or all. The default is any, and it 6 | # means that any rule is satisfied, the access is granted. If all is set, all rules must be satisfied. 7 | accessRuleLogic: ${access-control.accessRuleLogic:any} 8 | # If there is no access rule defined for the endpoint, default access is denied. Users can overwrite 9 | # this default action by setting this config value to false. If true, the handle will force users to 10 | # define the rules for each endpoint when the access control handler is enabled. 11 | defaultDeny: ${access-control.defaultDeny:true} 12 | # Define a list of path prefixes to skip the access-control to ease the configuration for the handler.yml 13 | # so that users can define some endpoint without fine-grained access-control security even through it uses 14 | # the default chain. This is useful if some endpoints want to skip the fine-grained access control in the 15 | # application. The format is a list of strings separated with commas or a JSON list in values.yml definition 16 | # from config server, or you can use yaml format in externalized access-control.yml file. 17 | skipPathPrefixes: ${access-control.skipPathPrefixes:} 18 | -------------------------------------------------------------------------------- /access-control/src/main/resources/config/access-control.yml: -------------------------------------------------------------------------------- 1 | # AccessControlHandler will be the last middleware handler before the proxy on the sidecar or the last 2 | # one before the business handler to handle the fine-grained authorization in the business domain. 3 | # Enable Access Control Handler 4 | enabled: ${access-control.enabled:true} 5 | # If there are multiple rules, the logic to combine them can be any or all. The default is any, and it 6 | # means that any rule is satisfied, the access is granted. If all is set, all rules must be satisfied. 7 | accessRuleLogic: ${access-control.accessRuleLogic:any} 8 | # If there is no access rule defined for the endpoint, default access is denied. Users can overwrite 9 | # this default action by setting this config value to false. If true, the handle will force users to 10 | # define the rules for each endpoint when the access control handler is enabled. 11 | defaultDeny: ${access-control.defaultDeny:true} 12 | # Define a list of path prefixes to skip the access-control to ease the configuration for the handler.yml 13 | # so that users can define some endpoint without fine-grained access-control security even through it uses 14 | # the default chain. This is useful if some endpoints want to skip the fine-grained access control in the 15 | # application. The format is a list of strings separated with commas or a JSON list in values.yml definition 16 | # from config server, or you can use yaml format in externalized access-control.yml file. 17 | skipPathPrefixes: ${access-control.skipPathPrefixes:} 18 | -------------------------------------------------------------------------------- /access-control/src/test/java/com/networknt/openapi/AccessControlConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Network New Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.networknt.openapi; 17 | 18 | import com.networknt.config.Config; 19 | import org.junit.Assert; 20 | import org.junit.Test; 21 | 22 | public class AccessControlConfigTest { 23 | 24 | @Test 25 | public void shouldLoadConfig() { 26 | AccessControlConfig config = (AccessControlConfig) Config.getInstance().getJsonObjectConfig(AccessControlConfig.CONFIG_NAME, AccessControlConfig.class); 27 | Assert.assertNotNull(config); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /access-control/src/test/java/com/networknt/openapi/AccessControlHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Network New Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.networknt.openapi; 18 | 19 | import org.junit.Test; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | /** 24 | * Test cases for the AccessControlHandler in a real test server. 25 | * 26 | * @author Steve Hu 27 | * 28 | */ 29 | public class AccessControlHandlerTest { 30 | static Logger logger = LoggerFactory.getLogger(AccessControlHandlerTest.class); 31 | 32 | @Test 33 | public void testRoleBasedAuth() { 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /access-control/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 21 | 22 | PROFILER 23 | 24 | NEUTRAL 25 | 26 | 27 | 28 | 30 | 31 | %d{HH:mm:ss.SSS} [%thread] %-5marker %-5level %logger{36} - %msg%n 32 | 33 | 34 | 35 | 36 | target/test.log 37 | false 38 | 39 | %d{HH:mm:ss.SSS} [%thread] %-5level %class{36}:%L %M - %msg%n 40 | 41 | 42 | 43 | 44 | 45 | target/audit.log 46 | 47 | %-5level [%thread] %date{ISO8601} %F:%L - %msg%n 48 | 49 | true 50 | 51 | 52 | target/audit.log.%i.zip 53 | 1 54 | 5 55 | 56 | 57 | 200MB 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /access-control/src/test/resources/server.yml: -------------------------------------------------------------------------------- 1 | serviceId: com.networknt.petstore-1.0.0 2 | -------------------------------------------------------------------------------- /openapi-config/pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 4.0.0 20 | 21 | 22 | com.networknt 23 | light-rest-4j 24 | 2.2.3-SNAPSHOT 25 | ../pom.xml 26 | 27 | 28 | openapi-config 29 | jar 30 | openapi-config 31 | An OpenAPI config module to support admin endpoint injection. 32 | 33 | 34 | 35 | 36 | com.networknt 37 | utility 38 | 39 | 40 | com.networknt 41 | config 42 | 43 | 44 | com.networknt 45 | json-overlay 46 | 47 | 48 | com.networknt 49 | openapi-parser 50 | 51 | 52 | com.fasterxml.jackson.core 53 | jackson-databind 54 | 55 | 56 | org.slf4j 57 | slf4j-api 58 | 59 | 60 | 61 | com.networknt 62 | client 63 | test 64 | 65 | 66 | ch.qos.logback 67 | logback-classic 68 | test 69 | 70 | 71 | junit 72 | junit 73 | test 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /openapi-config/src/main/java/com/networknt/openapi/DefaultInjectableSpecValidator.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | public class DefaultInjectableSpecValidator implements InjectableSpecValidator { 10 | public static String PATHS = "paths"; 11 | Logger logger = LoggerFactory.getLogger(DefaultInjectableSpecValidator.class); 12 | 13 | @Override 14 | public boolean isValid(Map openapi, Map inject) { 15 | // openapi.yaml can be null in the light-gateway with multiple specs are disabled. 16 | if (inject == null || openapi == null) { 17 | return true; 18 | } 19 | if (inject.get(PATHS) instanceof Map && openapi.get(PATHS) instanceof Map) { 20 | // each path in injecting spec 21 | for (Map.Entry injectPath : ((Map) inject.get(PATHS)).entrySet()) { 22 | // if the path in injecting spec also exists in the original openapi spec 23 | if (((Map) openapi.get(PATHS)).containsKey(injectPath.getKey())) { 24 | // get all the methods within the same path in the openapi spec and the injecting spec 25 | Map openapiPath = (Map) ((Map) openapi.get(PATHS)).get(injectPath.getKey()); 26 | Set openapiMethods = openapiPath.keySet(); 27 | Set injectMethods = ((Map) injectPath.getValue()).keySet(); 28 | // compare if there are duplicates 29 | for (String injectMethod : injectMethods) { 30 | if (openapiMethods.contains(injectMethod)) { 31 | logger.error("you are trying to overwritten an existing path: {} with method: {}", injectPath.getKey(), injectMethod); 32 | return false; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /openapi-config/src/main/java/com/networknt/openapi/InjectableSpecValidator.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi; 2 | 3 | import java.util.Map; 4 | 5 | public interface InjectableSpecValidator { 6 | boolean isValid(Map openapi, Map inject); 7 | } 8 | -------------------------------------------------------------------------------- /openapi-config/src/main/resources/config/openapi-handler-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema" : "http://json-schema.org/draft-07/schema#", 3 | "type" : "object", 4 | "required" : [ "multipleSpec", "ignoreInvalidPath", "pathSpecMapping" ], 5 | "properties" : { 6 | "multipleSpec" : { 7 | "type" : "boolean", 8 | "description" : "This configuration file is used to support multiple OpenAPI specifications in the same light-rest-4j instance.\nAn indicator to allow multiple openapi specifications. Default to false which only allow one spec named openapi.yml or openapi.yaml or openapi.json." 9 | }, 10 | "ignoreInvalidPath" : { 11 | "type" : "boolean", 12 | "description" : "When the OpenApiHandler is used in a shared gateway and some backend APIs have no specifications deployed on the gateway, the handler will return\nan invalid request path error to the client. To allow the call to pass through the OpenApiHandler and route to the backend APIs, you can set this\nflag to true. In this mode, the handler will only add the endpoint specification to the auditInfo if it can find it. Otherwise, it will pass through." 13 | }, 14 | "pathSpecMapping" : { 15 | "type" : "object", 16 | "description" : "Path to spec mapping. One or more base paths can map to the same specifications. The key is the base path and the value is the specification name.\nIf users want to use multiple specification files in the same instance, each specification must have a unique base path and it must be set as key.", 17 | "additionalProperties" : { 18 | "type" : "string" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /openapi-config/src/main/resources/config/openapi-handler.yaml: -------------------------------------------------------------------------------- 1 | # openapi-handler.yml 2 | # This configuration file is used to support multiple OpenAPI specifications in the same light-rest-4j instance. 3 | # An indicator to allow multiple openapi specifications. Default to false which only allow one spec named openapi.yml or openapi.yaml or openapi.json. 4 | multipleSpec: ${openapi-handler.multipleSpec:false} 5 | # When the OpenApiHandler is used in a shared gateway and some backend APIs have no specifications deployed on the gateway, the handler will return 6 | # an invalid request path error to the client. To allow the call to pass through the OpenApiHandler and route to the backend APIs, you can set this 7 | # flag to true. In this mode, the handler will only add the endpoint specification to the auditInfo if it can find it. Otherwise, it will pass through. 8 | ignoreInvalidPath: ${openapi-handler.ignoreInvalidPath:false} 9 | # Path to spec mapping. One or more base paths can map to the same specifications. The key is the base path and the value is the specification name. 10 | # If users want to use multiple specification files in the same instance, each specification must have a unique base path and it must be set as key. 11 | pathSpecMapping: ${openapi-handler.pathSpecMapping:} 12 | -------------------------------------------------------------------------------- /openapi-config/src/main/resources/config/openapi-handler.yml: -------------------------------------------------------------------------------- 1 | # openapi-handler.yml 2 | # This configuration file is used to support multiple OpenAPI specifications in the same light-rest-4j instance. 3 | # An indicator to allow multiple openapi specifications. Default to false which only allow one spec named openapi.yml or openapi.yaml or openapi.json. 4 | multipleSpec: ${openapi-handler.multipleSpec:false} 5 | # When the OpenApiHandler is used in a shared gateway and some backend APIs have no specifications deployed on the gateway, the handler will return 6 | # an invalid request path error to the client. To allow the call to pass through the OpenApiHandler and route to the backend APIs, you can set this 7 | # flag to true. In this mode, the handler will only add the endpoint specification to the auditInfo if it can find it. Otherwise, it will pass through. 8 | ignoreInvalidPath: ${openapi-handler.ignoreInvalidPath:false} 9 | # Path to spec mapping. One or more base paths can map to the same specifications. The key is the base path and the value is the specification name. 10 | # If users want to use multiple specification files in the same instance, each specification must have a unique base path and it must be set as key. 11 | pathSpecMapping: ${openapi-handler.pathSpecMapping:} 12 | # v1: openapi-v1 13 | # v2: openapi-v2 14 | -------------------------------------------------------------------------------- /openapi-config/src/test/java/com/networknt/openapi/DefaultInjectableSpecValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi; 2 | 3 | import com.networknt.config.Config; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | import java.util.Map; 8 | 9 | public class DefaultInjectableSpecValidatorTest { 10 | @Test 11 | public void testValidatorInValid() { 12 | Map openapi = Config.getInstance().getJsonMapConfig("openapi"); 13 | Map inject = Config.getInstance().getJsonMapConfig("openapi-inject-test-neg"); 14 | DefaultInjectableSpecValidator validator = new DefaultInjectableSpecValidator(); 15 | boolean isValid = validator.isValid(openapi, inject); 16 | Assert.assertFalse(isValid); 17 | } 18 | 19 | @Test 20 | public void testValidatorValid() { 21 | Map openapi = Config.getInstance().getJsonMapConfig("openapi"); 22 | Map inject = Config.getInstance().getJsonMapConfig("openapi-inject-test-pos"); 23 | DefaultInjectableSpecValidator validator = new DefaultInjectableSpecValidator(); 24 | boolean isValid = validator.isValid(openapi, inject); 25 | Assert.assertTrue(isValid); 26 | } 27 | 28 | @Test 29 | public void testNoInject() { 30 | Map openapi = Config.getInstance().getJsonMapConfig("openapi"); 31 | Map inject = null; 32 | DefaultInjectableSpecValidator validator = new DefaultInjectableSpecValidator(); 33 | boolean isValid = validator.isValid(openapi, inject); 34 | Assert.assertTrue(isValid); 35 | } 36 | 37 | @Test 38 | public void testValidatorDuplicatePath() { 39 | Map openapi = Config.getInstance().getJsonMapConfig("openapi"); 40 | Map inject = Config.getInstance().getJsonMapConfig("openapi-inject-test-dup-path"); 41 | DefaultInjectableSpecValidator validator = new DefaultInjectableSpecValidator(); 42 | boolean isValid = validator.isValid(openapi, inject); 43 | Assert.assertTrue(isValid); 44 | } 45 | 46 | @Test 47 | public void testValidatorDuplicateMethod() { 48 | Map openapi = Config.getInstance().getJsonMapConfig("openapi"); 49 | Map inject = Config.getInstance().getJsonMapConfig("openapi-inject-test-dup-method"); 50 | DefaultInjectableSpecValidator validator = new DefaultInjectableSpecValidator(); 51 | boolean isValid = validator.isValid(openapi, inject); 52 | Assert.assertFalse(isValid); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /openapi-config/src/test/java/com/networknt/openapi/OpenApiHandlerConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class OpenApiHandlerConfigTest { 7 | @Test 8 | public void testLoadConfig() { 9 | OpenApiHandlerConfig config = OpenApiHandlerConfig.load(); 10 | Assert.assertEquals(3, config.getMappedConfig().size()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/client.truststore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networknt/light-rest-4j/27c19bbaf3ad3792007aa89dc6b8f018c52178c0/openapi-config/src/test/resources/config/client.truststore -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/handler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | enabled: true 3 | 4 | # Configuration for the LightHttpHandler. The handler is the base class for all middleware, server and health handlers 5 | # set the Status Object in the AUDIT_INFO, for auditing purposes 6 | # default, if not set:false 7 | auditOnError: true 8 | 9 | # set the StackTrace in the AUDIT_INFO, for auditing purposes 10 | # default, if not set:false 11 | auditStackTrace: true 12 | 13 | handlers: 14 | - com.networknt.handler.sample.SampleHttpHandler1 15 | - com.networknt.handler.sample.SampleHttpHandler2 16 | - com.networknt.handler.sample.SampleHttpHandler3@third 17 | 18 | chains: 19 | secondBeforeFirst: 20 | - com.networknt.handler.sample.SampleHttpHandler2 21 | - com.networknt.handler.sample.SampleHttpHandler1 22 | 23 | paths: 24 | - path: '/test' 25 | method: 'get' 26 | exec: 27 | - secondBeforeFirst 28 | - third 29 | - path: '/v2/health' 30 | method: 'post' 31 | exec: 32 | - secondBeforeFirst 33 | - third 34 | # If there is no matched path, then it goes here first. If this is not set, then an error 35 | # will be returned. 36 | defaultHandlers: 37 | - third 38 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/openapi-handler-multiple.yml: -------------------------------------------------------------------------------- 1 | # openapi-handler.yml 2 | # This configuration file is used to support multiple OpenAPI specifications in the same light-rest-4j instance. 3 | # An indicator to allow multiple openapi specifications. Default to false which only allow one spec named openapi.yml or openapi.yaml or openapi.json. 4 | multipleSpec: ${openapi-handler.multipleSpec:true} 5 | # When the OpenApiHandler is used in a shared gateway and some backend APIs have no specifications deployed on the gateway, the handler will return 6 | # an invalid request path error to the client. To allow the call to pass through the OpenApiHandler and route to the backend APIs, you can set this 7 | # flag to true. In this mode, the handler will only add the endpoint specification to the auditInfo if it can find it. Otherwise, it will pass through. 8 | ignoreInvalidPath: ${openapi-handler.ignoreInvalidPath:false} 9 | # Path to spec mapping. One or more base paths can map to the same specifications. The key is the base path and the value is the specification name. 10 | # If users want to use multiple specification files in the same instance, each specification must have a unique base path and it must be set as key. 11 | pathSpecMapping: 12 | /petstore: openapi-petstore 13 | /market: openapi-market 14 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/openapi-inject-test-dup-method.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | paths: 3 | /pets/{petId}: 4 | get: 5 | security: 6 | - api-scope: 7 | - admim 8 | 9 | components: 10 | securitySchemes: 11 | api-scope: 12 | flows: 13 | clientCredentials: 14 | scopes: 15 | admin: orwritten 16 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/openapi-inject-test-dup-path.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | paths: 3 | /pets/{petId}: 4 | # the original spec does not contain put method 5 | put: 6 | security: 7 | - api-scope: 8 | - admim 9 | 10 | components: 11 | securitySchemes: 12 | api-scope: 13 | flows: 14 | clientCredentials: 15 | scopes: 16 | admin: orwritten 17 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/openapi-inject-test-neg.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | paths: 3 | /pets/${badKey}: 4 | get: 5 | security: 6 | - api-scope: 7 | - admim 8 | 9 | components: 10 | securitySchemes: 11 | api-scope: 12 | flows: 13 | clientCredentials: 14 | scopes: 15 | admin: orwritten 16 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/openapi-inject-test-pos.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | paths: 3 | /pets/${goodKey}: 4 | get: 5 | security: 6 | - api-scope: 7 | - admim 8 | 9 | components: 10 | securitySchemes: 11 | api-scope: 12 | flows: 13 | clientCredentials: 14 | scopes: 15 | admin: orwritten 16 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/openapi-market.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | version: 1.0.0 4 | title: Swagger Market 5 | license: 6 | name: MIT 7 | servers: 8 | - url: 'http://market.swagger.io/market' 9 | paths: 10 | /{store}/products: 11 | get: 12 | summary: Get all products from stores 13 | operationId: listProducts 14 | tags: 15 | - products 16 | parameters: 17 | - name: limit 18 | in: query 19 | description: How many items to return at one time (max 100) 20 | required: false 21 | schema: 22 | type: integer 23 | format: int32 24 | - name: store 25 | in: path 26 | description: The downstream store name 27 | required: true 28 | schema: 29 | type: string 30 | security: 31 | - market_auth: 32 | - 'read:products' 33 | responses: 34 | '200': 35 | description: An paged array of products 36 | content: 37 | application/json: 38 | schema: 39 | type: array 40 | items: 41 | $ref: '#/components/schemas/Product' 42 | example: 43 | - id: 1 44 | name: catten 45 | tag: cat 46 | - id: 2 47 | name: doggy 48 | tag: dog 49 | default: 50 | description: unexpected error 51 | content: 52 | application/json: 53 | schema: 54 | $ref: '#/components/schemas/Error' 55 | post: 56 | summary: Create a product 57 | operationId: createProducts 58 | parameters: 59 | - name: store 60 | in: path 61 | description: The downstream store name 62 | required: true 63 | schema: 64 | type: string 65 | requestBody: 66 | description: Product to add to the target store 67 | required: true 68 | content: 69 | application/json: 70 | schema: 71 | $ref: '#/components/schemas/Product' 72 | tags: 73 | - products 74 | security: 75 | - market_auth: 76 | - 'read:products' 77 | - 'write:products' 78 | responses: 79 | '201': 80 | description: Null response 81 | default: 82 | description: unexpected error 83 | content: 84 | application/json: 85 | schema: 86 | $ref: '#/components/schemas/Error' 87 | components: 88 | securitySchemes: 89 | market_auth: 90 | type: oauth2 91 | description: This API uses OAuth 2 with the client credential grant flow. 92 | flows: 93 | clientCredentials: 94 | tokenUrl: 'https://localhost:6882/token' 95 | scopes: 96 | 'write:products': modify products 97 | 'read:products': read your products 98 | schemas: 99 | Product: 100 | type: object 101 | required: 102 | - id 103 | - name 104 | properties: 105 | id: 106 | type: integer 107 | format: int64 108 | name: 109 | type: string 110 | tag: 111 | type: string 112 | Error: 113 | type: object 114 | required: 115 | - code 116 | - message 117 | properties: 118 | code: 119 | type: integer 120 | format: int32 121 | message: 122 | type: string 123 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/openapi-petstore.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | version: 1.0.0 4 | title: Swagger Petstore 5 | license: 6 | name: MIT 7 | servers: 8 | - url: 'http://petstore.swagger.io/petstore' 9 | paths: 10 | /pets: 11 | get: 12 | summary: List all pets 13 | operationId: listPets 14 | tags: 15 | - pets 16 | parameters: 17 | - name: limit 18 | in: query 19 | description: How many items to return at one time (max 100) 20 | required: false 21 | schema: 22 | type: integer 23 | format: int32 24 | security: 25 | - petstore_auth: 26 | - 'read:pets' 27 | responses: 28 | '200': 29 | description: An paged array of pets 30 | headers: 31 | x-next: 32 | description: A link to the next page of responses 33 | schema: 34 | type: string 35 | content: 36 | application/json: 37 | schema: 38 | type: array 39 | items: 40 | $ref: '#/components/schemas/Pet' 41 | example: 42 | - id: 1 43 | name: catten 44 | tag: cat 45 | - id: 2 46 | name: doggy 47 | tag: dog 48 | default: 49 | description: unexpected error 50 | content: 51 | application/json: 52 | schema: 53 | $ref: '#/components/schemas/Error' 54 | post: 55 | summary: Create a pet 56 | operationId: createPets 57 | requestBody: 58 | description: Pet to add to the store 59 | required: true 60 | content: 61 | application/json: 62 | schema: 63 | $ref: '#/components/schemas/Pet' 64 | tags: 65 | - pets 66 | security: 67 | - petstore_auth: 68 | - 'read:pets' 69 | - 'write:pets' 70 | responses: 71 | '201': 72 | description: Null response 73 | default: 74 | description: unexpected error 75 | content: 76 | application/json: 77 | schema: 78 | $ref: '#/components/schemas/Error' 79 | '/pets/{petId}': 80 | get: 81 | summary: Info for a specific pet 82 | operationId: showPetById 83 | tags: 84 | - pets 85 | parameters: 86 | - name: petId 87 | in: path 88 | required: true 89 | description: The id of the pet to retrieve 90 | schema: 91 | type: string 92 | security: 93 | - petstore_auth: 94 | - 'read:pets' 95 | responses: 96 | '200': 97 | description: Expected response to a valid request 98 | content: 99 | application/json: 100 | schema: 101 | $ref: '#/components/schemas/Pet' 102 | example: 103 | id: 1 104 | name: Jessica Right 105 | tag: pet 106 | default: 107 | description: unexpected error 108 | content: 109 | application/json: 110 | schema: 111 | $ref: '#/components/schemas/Error' 112 | delete: 113 | summary: Delete a specific pet 114 | operationId: deletePetById 115 | tags: 116 | - pets 117 | parameters: 118 | - name: petId 119 | in: path 120 | required: true 121 | description: The id of the pet to delete 122 | schema: 123 | type: string 124 | - name: key 125 | in: header 126 | required: true 127 | description: The key header 128 | schema: 129 | type: string 130 | security: 131 | - petstore_auth: 132 | - 'write:pets' 133 | responses: 134 | '200': 135 | description: Expected response to a valid request 136 | content: 137 | application/json: 138 | schema: 139 | $ref: '#/components/schemas/Pet' 140 | examples: 141 | response: 142 | value: 143 | id: 1 144 | name: Jessica Right 145 | tag: pet 146 | default: 147 | description: unexpected error 148 | content: 149 | application/json: 150 | schema: 151 | $ref: '#/components/schemas/Error' 152 | /notifications: 153 | get: 154 | summary: Get Notifications 155 | operationId: listNotifications 156 | tags: 157 | - notifications 158 | security: 159 | - petstore_auth: 160 | - 'read:pets' 161 | responses: 162 | '200': 163 | description: A standard notification response in JSON for response interceptor test 164 | 165 | /flowers: 166 | post: 167 | summary: The API accept XML and the consumer is using JSON 168 | operationId: flowers 169 | tags: 170 | - flowers 171 | security: 172 | - petstore_auth: 173 | - 'read:pets' 174 | responses: 175 | '200': 176 | description: Return an flowers XML as the demo soap service 177 | 178 | components: 179 | securitySchemes: 180 | petstore_auth: 181 | type: oauth2 182 | description: This API uses OAuth 2 with the client credential grant flow. 183 | flows: 184 | clientCredentials: 185 | tokenUrl: 'https://localhost:6882/token' 186 | scopes: 187 | 'write:pets': modify pets in your account 188 | 'read:pets': read your pets 189 | schemas: 190 | Pet: 191 | type: object 192 | required: 193 | - id 194 | - name 195 | properties: 196 | id: 197 | type: integer 198 | format: int64 199 | name: 200 | type: string 201 | tag: 202 | type: string 203 | Error: 204 | type: object 205 | required: 206 | - code 207 | - message 208 | properties: 209 | code: 210 | type: integer 211 | format: int32 212 | message: 213 | type: string 214 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/config/values.yml: -------------------------------------------------------------------------------- 1 | # server.yml 2 | server.serviceId: com.networknt.petstore-1.0.0 3 | badKey: "{petId}" 4 | goodKey: petId 5 | -------------------------------------------------------------------------------- /openapi-config/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | TODO create logger for audit only. 20 | http://stackoverflow.com/questions/2488558/logback-to-log-different-messages-to-two-files 21 | 22 | PROFILER 23 | 24 | NEUTRAL 25 | 26 | 27 | 28 | 30 | 31 | %d{HH:mm:ss.SSS} [%thread] %-5marker %-5level %logger{36} - %msg%n 32 | 33 | 34 | 35 | 36 | target/test.log 37 | false 38 | 39 | %d{HH:mm:ss.SSS} [%thread] %-5level %class{36}:%L %M - %msg%n 40 | 41 | 42 | 43 | 44 | 45 | target/audit.log 46 | 47 | %-5level [%thread] %date{ISO8601} %F:%L - %msg%n 48 | true 49 | 50 | 51 | target/audit.log.%i.zip 52 | 1 53 | 5 54 | 55 | 56 | 200MB 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /openapi-meta/pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 4.0.0 20 | 21 | 22 | com.networknt 23 | light-rest-4j 24 | 2.2.3-SNAPSHOT 25 | ../pom.xml 26 | 27 | 28 | openapi-meta 29 | jar 30 | openapi-meta 31 | An OpenAPI Specification 3.0 meta handler module that cache the spec and attached context related operation to the request based on uri and method. 32 | 33 | 34 | 35 | 36 | com.networknt 37 | utility 38 | 39 | 40 | com.networknt 41 | http-string 42 | 43 | 44 | com.networknt 45 | config 46 | 47 | 48 | com.networknt 49 | status 50 | 51 | 52 | com.networknt 53 | audit 54 | 55 | 56 | com.networknt 57 | handler 58 | 59 | 60 | com.networknt 61 | json-overlay 62 | 63 | 64 | com.networknt 65 | openapi-parser 66 | 67 | 68 | com.networknt 69 | openapi-config 70 | 71 | 72 | com.fasterxml.jackson.core 73 | jackson-databind 74 | 75 | 76 | org.slf4j 77 | slf4j-api 78 | 79 | 80 | org.owasp.encoder 81 | encoder 82 | 83 | 84 | io.undertow 85 | undertow-core 86 | 87 | 88 | 89 | com.networknt 90 | client 91 | test 92 | 93 | 94 | ch.qos.logback 95 | logback-classic 96 | test 97 | 98 | 99 | junit 100 | junit 101 | test 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/OpenApiEndpointSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Network New Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.networknt.openapi; 17 | 18 | import com.networknt.handler.config.EndpointSource; 19 | import com.networknt.oas.model.Path; 20 | import com.networknt.oas.model.Server; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.net.URL; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | /** 30 | * Lists endpoints defined in the OpenApi spec parsed by OpenApiHelper. 31 | */ 32 | public class OpenApiEndpointSource implements EndpointSource { 33 | 34 | private static final Logger log = LoggerFactory.getLogger(OpenApiEndpointSource.class); 35 | 36 | private OpenApiHelper helper; 37 | 38 | public OpenApiEndpointSource(OpenApiHelper helper) { 39 | this.helper = helper; 40 | } 41 | 42 | @Override 43 | public Iterable listEndpoints() { 44 | 45 | List endpoints = new ArrayList<>(); 46 | String basePath = findBasePath(); 47 | Map paths = helper.openApi3.getPaths(); 48 | 49 | 50 | if(log.isInfoEnabled()) log.info("Generating paths from OpenApi spec"); 51 | for (Map.Entry pathPair : paths.entrySet()) { 52 | String path = pathPair.getKey(); 53 | for (String method : pathPair.getValue().getOperations().keySet()) { 54 | Endpoint endpoint = new Endpoint(basePath + path, method); 55 | if(log.isDebugEnabled()) log.debug(endpoint.toString()); 56 | endpoints.add(endpoint); 57 | } 58 | } 59 | return endpoints; 60 | } 61 | 62 | public String findBasePath() { 63 | List servers = helper.openApi3.getServers(); 64 | if(servers.isEmpty()) { 65 | log.warn("No server found in OpenApi spec. Using empty base path for API."); 66 | return ""; 67 | } 68 | 69 | Server server = servers.get(0); 70 | String url = null; 71 | try { 72 | url = server.getUrl(); 73 | URL urlObj = new URL(url); 74 | String basePath = urlObj.getPath(); 75 | while (basePath.endsWith("/")) { 76 | basePath = basePath.substring(0, basePath.length() - 1); 77 | } 78 | return basePath; 79 | } catch (Exception e) { 80 | throw new RuntimeException("Malformed servers[0].url in OpenApi spec: " + url, e); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/CookieParameterDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import com.networknt.oas.model.Parameter; 9 | import com.networknt.openapi.OpenApiHandler; 10 | import com.networknt.utility.StringUtils; 11 | 12 | import io.undertow.UndertowOptions; 13 | import io.undertow.server.HttpServerExchange; 14 | import io.undertow.server.handlers.Cookie; 15 | import io.undertow.util.AttachmentKey; 16 | import io.undertow.util.Headers; 17 | 18 | public class CookieParameterDeserializer implements ParameterDeserializer { 19 | private static final String FORM="form"; 20 | 21 | @Override 22 | public AttachmentKey> getAttachmentKey(){ 23 | return OpenApiHandler.DESERIALIZED_COOKIE_PARAMETERS; 24 | } 25 | 26 | @Override 27 | public StyleParameterDeserializer getStyleDeserializer(String style) { 28 | if (StringUtils.isNotBlank(style) && !FORM.equalsIgnoreCase(style)) { 29 | return null; 30 | } 31 | 32 | return new StyleParameterDeserializer() { 33 | @Override 34 | public boolean isApplicable(ValueType valueType, boolean exploade) { 35 | return !exploade || StyleParameterDeserializer.super.isApplicable(valueType, exploade); 36 | } 37 | 38 | @Override 39 | public Object deserialize(HttpServerExchange exchange, Parameter parameter, ValueType valueType, 40 | boolean exploade) { 41 | List rawCookies = exchange.getRequestHeaders().get(Headers.COOKIE); 42 | 43 | 44 | Map cookies = CookieHelper.parseRequestCookies( 45 | exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_COOKIES, 200), 46 | exchange.getConnection().getUndertowOptions().get(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false), 47 | rawCookies); 48 | 49 | 50 | Cookie cookie = cookies.get(parameter.getName()); 51 | 52 | String value = cookie.getValue(); 53 | 54 | if (ValueType.ARRAY == valueType) { 55 | List valueList = new ArrayList<>(); 56 | 57 | valueList.addAll(asList(value, Delimiters.COMMA)); 58 | 59 | return valueList; 60 | }else if (ValueType.OBJECT == valueType) { 61 | Map valueMap = new HashMap<>(); 62 | valueMap.putAll(asMap(value, Delimiters.COMMA)); 63 | 64 | return valueMap; 65 | } 66 | 67 | return null; 68 | } 69 | 70 | }; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/Delimiters.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | public class Delimiters { 4 | static final String COMMA=","; 5 | static final String SPACE=" "; 6 | static final String PIPE="|"; 7 | static final String DOT="."; 8 | static final String SEMICOLON=";"; 9 | static final String EQUAL="="; 10 | static final String LEFT_BRACKET="["; 11 | static final String SLASH="/"; 12 | } 13 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/HeaderParameterDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import com.networknt.oas.model.Parameter; 10 | import com.networknt.openapi.OpenApiHandler; 11 | import com.networknt.utility.StringUtils; 12 | 13 | import io.undertow.server.HttpServerExchange; 14 | import io.undertow.util.AttachmentKey; 15 | import io.undertow.util.HttpString; 16 | 17 | public class HeaderParameterDeserializer implements ParameterDeserializer { 18 | private static final String SIMPLE="simple"; 19 | 20 | @Override 21 | public AttachmentKey> getAttachmentKey(){ 22 | return OpenApiHandler.DESERIALIZED_HEADER_PARAMETERS; 23 | } 24 | 25 | @Override 26 | public StyleParameterDeserializer getStyleDeserializer(String style) { 27 | if (StringUtils.isNotBlank(style) && !SIMPLE.equalsIgnoreCase(style)) { 28 | return null; 29 | } 30 | 31 | return new StyleParameterDeserializer() { 32 | 33 | @Override 34 | public Object deserialize(HttpServerExchange exchange, Parameter parameter, 35 | ValueType valueType, 36 | boolean exploade) { 37 | Collection values = exchange.getRequestHeaders().get(new HttpString(parameter.getName())); 38 | 39 | if (ValueType.ARRAY == valueType) { 40 | List valueList = new ArrayList<>(); 41 | 42 | values.forEach(v->valueList.addAll(asList(v, Delimiters.COMMA))); 43 | 44 | return valueList; 45 | }else if (ValueType.OBJECT == valueType) { 46 | Map valueMap = new HashMap<>(); 47 | values.forEach(v->valueMap.putAll(exploade?asExploadeMap(v, Delimiters.COMMA):asMap(v, Delimiters.COMMA))); 48 | 49 | return valueMap; 50 | } 51 | 52 | return null; 53 | } 54 | 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/ParameterDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import java.util.*; 4 | import java.util.stream.Collectors; 5 | 6 | import com.networknt.oas.model.Parameter; 7 | import com.networknt.openapi.OpenApiOperation; 8 | import io.undertow.server.HttpServerExchange; 9 | import io.undertow.server.handlers.Cookie; 10 | import io.undertow.util.AttachmentKey; 11 | import io.undertow.util.Cookies; 12 | 13 | public interface ParameterDeserializer { 14 | static Set getCandidateQueryParams(HttpServerExchange exchange){ 15 | Set candidateQueryParams = new HashSet<>(); 16 | 17 | exchange.getQueryParameters().keySet().forEach(key->{ 18 | if (!key.contains(Delimiters.LEFT_BRACKET)) { 19 | candidateQueryParams.add(key); 20 | }else {// for deepObject serialization 21 | candidateQueryParams.add(key.substring(0, key.indexOf(Delimiters.LEFT_BRACKET))); 22 | } 23 | }); 24 | 25 | return candidateQueryParams; 26 | } 27 | 28 | static void deserialize(HttpServerExchange exchange, OpenApiOperation openApiOperation) { 29 | Set candidateQueryParams = getCandidateQueryParams(exchange); 30 | Set candidatePathParams = exchange.getPathParameters().keySet(); 31 | Set candidateHeaderParams = exchange.getRequestHeaders().getHeaderNames().stream().map(name->name.toString()).collect(Collectors.toSet()); 32 | List cookieNames = new ArrayList<>(); 33 | exchange.requestCookies().forEach(s -> cookieNames.add(s.getName())); 34 | Set candidateCookieParams = new HashSet(cookieNames); 35 | 36 | openApiOperation.getOperation().getParameters().forEach(p->{ 37 | ParameterType type = ParameterType.of(p.getIn()); 38 | 39 | if (null!=type) { 40 | ParameterDeserializer deserializer = type.getDeserializer(); 41 | 42 | switch(type){ 43 | case QUERY: 44 | deserializer.deserialize(exchange, p, candidateQueryParams); 45 | break; 46 | case PATH: 47 | deserializer.deserialize(exchange, p, candidatePathParams); 48 | break; 49 | case HEADER: 50 | deserializer.deserialize(exchange, p, candidateHeaderParams); 51 | break; 52 | case COOKIE: 53 | deserializer.deserialize(exchange, p, candidateCookieParams); 54 | break; 55 | } 56 | } 57 | }); 58 | } 59 | 60 | default boolean isApplicable(HttpServerExchange exchange, Parameter parameter, Set candidateParams) { 61 | // HTTP header names are case insensitive (RFC 7230, https://tools.ietf.org/html/rfc7230#section-3.2) 62 | if(ParameterType.of(parameter.getIn()) == ParameterType.HEADER) 63 | return candidateParams.stream().anyMatch(s->parameter.getName().equalsIgnoreCase(s)); 64 | else 65 | return candidateParams.contains(parameter.getName()); 66 | } 67 | 68 | default void deserialize(HttpServerExchange exchange, Parameter parameter, Set candidateParams) { 69 | if (!isApplicable(exchange, parameter, candidateParams)) { 70 | return; 71 | } 72 | 73 | StyleParameterDeserializer deserializer = getStyleDeserializer(parameter.getStyle()); 74 | 75 | if (null==deserializer) { 76 | return; 77 | } 78 | 79 | Object valueObj = deserializer.deserialize(exchange, parameter); 80 | 81 | if (null!=valueObj) { 82 | attach(exchange, parameter.getName(), valueObj); 83 | } 84 | } 85 | 86 | StyleParameterDeserializer getStyleDeserializer(String style); 87 | 88 | default AttachmentKey> getAttachmentKey(){ 89 | return null; 90 | } 91 | 92 | default void attach(HttpServerExchange exchange, String key, Object value) { 93 | AttachmentKey> paramType = getAttachmentKey(); 94 | 95 | if (null!=paramType) { 96 | attach(exchange, paramType, key, value); 97 | } 98 | } 99 | 100 | default void attach(HttpServerExchange exchange, AttachmentKey> paramType, String key, Object value) { 101 | Map paramMap = exchange.getAttachment(paramType); 102 | 103 | if (null == paramMap) { 104 | exchange.putAttachment(paramType, paramMap=new HashMap<>()); 105 | } 106 | 107 | paramMap.put(key, value); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/ParameterType.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import com.networknt.utility.StringUtils; 7 | 8 | public enum ParameterType { 9 | PATH(new PathParameterDeserializer()), 10 | QUERY(new QueryParameterDeserializer()), 11 | HEADER(new HeaderParameterDeserializer()), 12 | COOKIE(new CookieParameterDeserializer()); 13 | 14 | private static Map lookup = new HashMap<>(); 15 | 16 | private final ParameterDeserializer deserializer; 17 | 18 | private ParameterType(ParameterDeserializer deserializer) { 19 | this.deserializer = deserializer; 20 | } 21 | 22 | static { 23 | for (ParameterType type: ParameterType.values()) { 24 | lookup.put(type.name(), type); 25 | } 26 | } 27 | 28 | public static ParameterType of(String typeStr) { 29 | return lookup.get(StringUtils.trimToEmpty(typeStr).toUpperCase()); 30 | } 31 | 32 | public static boolean is(String typeStr, ParameterType type) { 33 | return type == of(typeStr); 34 | } 35 | 36 | public ParameterDeserializer getDeserializer() { 37 | return this.deserializer; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/PathParameterDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import com.networknt.oas.model.Parameter; 10 | import com.networknt.oas.model.Schema; 11 | import com.networknt.openapi.OpenApiHandler; 12 | import com.networknt.utility.StringUtils; 13 | 14 | import io.undertow.server.HttpServerExchange; 15 | import io.undertow.util.AttachmentKey; 16 | 17 | public class PathParameterDeserializer implements ParameterDeserializer{ 18 | @Override 19 | public AttachmentKey> getAttachmentKey(){ 20 | return OpenApiHandler.DESERIALIZED_PATH_PARAMETERS; 21 | } 22 | 23 | @Override 24 | public StyleParameterDeserializer getStyleDeserializer(String style) { 25 | PathParameterStyle styleDef = PathParameterStyle.of(style); 26 | 27 | if (null==styleDef) { 28 | return null; 29 | } 30 | 31 | return styleDef.getDeserializer(); 32 | } 33 | } 34 | 35 | class SimpleStyleDeserializer implements StyleParameterDeserializer{ 36 | 37 | @Override 38 | public Object deserialize(HttpServerExchange exchange, Parameter parameter, ValueType valueType, 39 | boolean exploade) { 40 | 41 | Collection values = exchange.getPathParameters().get(parameter.getName()); 42 | 43 | if (ValueType.ARRAY == valueType) { 44 | List valueList = new ArrayList<>(); 45 | 46 | values.forEach(v->valueList.addAll(asList(v, Delimiters.COMMA))); 47 | 48 | return valueList; 49 | }else if (ValueType.OBJECT == valueType) { 50 | Map valueMap = new HashMap<>(); 51 | values.forEach(v->valueMap.putAll(exploade?asExploadeMap(v, Delimiters.COMMA):asMap(v, Delimiters.COMMA))); 52 | 53 | return valueMap; 54 | } 55 | 56 | return null; 57 | } 58 | } 59 | 60 | class LabelStyleDeserializer implements StyleParameterDeserializer{ 61 | 62 | @Override 63 | public Object deserialize(HttpServerExchange exchange, Parameter parameter, ValueType valueType, 64 | boolean exploade) { 65 | Collection values = exchange.getPathParameters().get(parameter.getName()); 66 | String delimiter = exploade?Delimiters.DOT:Delimiters.COMMA; 67 | 68 | if (ValueType.ARRAY == valueType) { 69 | List valueList = new ArrayList<>(); 70 | 71 | values.forEach(v->valueList.addAll(asList(trimStart(v, Delimiters.DOT), delimiter))); 72 | 73 | return valueList; 74 | }else if (ValueType.OBJECT == valueType) { 75 | Map valueMap = new HashMap<>(); 76 | values.forEach(v->valueMap.putAll(exploade?asExploadeMap(trimStart(v, Delimiters.DOT), delimiter):asMap(trimStart(v, Delimiters.DOT), delimiter))); 77 | 78 | return valueMap; 79 | } 80 | 81 | return null; 82 | } 83 | } 84 | 85 | class MatrixStyleDeserializer implements StyleParameterDeserializer{ 86 | @Override 87 | public Object deserialize(HttpServerExchange exchange, Parameter parameter, ValueType valueType, 88 | boolean exploade) { 89 | Collection values = exchange.getPathParameters().get(parameter.getName()); 90 | String delimiter = exploade?Delimiters.SEMICOLON:Delimiters.COMMA; 91 | String start = String.format("%s%s=", Delimiters.SEMICOLON, parameter.getName()); 92 | 93 | if (ValueType.PRIMITIVE == valueType) { 94 | StringBuilder builder = new StringBuilder(); 95 | values.forEach(v->builder.append(trimStart(v, start))); 96 | return builder.toString(); 97 | }else if (ValueType.ARRAY == valueType) { 98 | List valueList = new ArrayList<>(); 99 | 100 | if (!exploade) { 101 | values.forEach(v->valueList.addAll(asList(trimStart(v, start), delimiter))); 102 | }else { 103 | String prefix = String.format("%s=", parameter.getName()); 104 | values.forEach(v->valueList.addAll(asList(replace(trimStart(v, Delimiters.SEMICOLON), prefix,StringUtils.EMPTY), delimiter))); 105 | } 106 | 107 | if (StringUtils.isBlank(valueList.get(valueList.size()-1))) { 108 | // this is a undertow-specific trick. 109 | // undertow parses matrix style path parameters and removes path parameters from request path 110 | // as a result, a space is added by com.networknt.handler.Handler.start() 111 | 112 | valueList.remove(valueList.size()-1); 113 | } 114 | 115 | return valueList; 116 | }else if (ValueType.OBJECT == valueType) { 117 | Map valueMap = new HashMap<>(); 118 | 119 | if (!exploade) { 120 | values.forEach(v->valueMap.putAll(asMap(v, delimiter))); 121 | }else { 122 | Schema schema = parameter.getSchema(); 123 | String requestURI = exchange.getRequestURI(); 124 | 125 | schema.getProperties().keySet().forEach(k->valueMap.put(k, getValue(k, requestURI))); 126 | } 127 | 128 | return valueMap; 129 | } 130 | 131 | return null; 132 | } 133 | 134 | private String getValue(String prop, String uri) { 135 | String key = String.format(";%s=", prop); 136 | 137 | if (StringUtils.containsIgnoreCase(uri, key)) { 138 | String value = uri.substring(uri.indexOf(key) + key.length()); 139 | int nextSemiColon = value.indexOf(Delimiters.SEMICOLON); 140 | int nextSlash = value.indexOf(Delimiters.SLASH); 141 | int end = Math.min(nextSemiColon, nextSlash); 142 | 143 | if (nextSemiColon>=0 && nextSlash>=0) { 144 | value = value.substring(0, end); 145 | }else if (nextSemiColon>=0) { 146 | value = value.substring(0, nextSemiColon); 147 | }else if (nextSlash>=0) { 148 | value = value.substring(0, nextSlash); 149 | } 150 | 151 | return value; 152 | } 153 | 154 | return StringUtils.EMPTY; 155 | } 156 | 157 | @Override 158 | public boolean isApplicable(ValueType valueType, boolean exploade) { 159 | return null!=valueType; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/PathParameterStyle.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import com.networknt.utility.StringUtils; 7 | 8 | public enum PathParameterStyle { 9 | SIMPLE(new SimpleStyleDeserializer()), 10 | LABEL(new LabelStyleDeserializer()), 11 | MATRIX(new MatrixStyleDeserializer()); 12 | 13 | private static Map lookup = new HashMap<>(); 14 | private final StyleParameterDeserializer deserializer; 15 | 16 | private PathParameterStyle(StyleParameterDeserializer deserializer) { 17 | this.deserializer = deserializer; 18 | } 19 | 20 | static { 21 | for (PathParameterStyle style: PathParameterStyle.values()) { 22 | lookup.put(style.name(), style); 23 | } 24 | } 25 | 26 | public static PathParameterStyle of(String styleStr) { 27 | if (StringUtils.isBlank(styleStr)) { 28 | return SIMPLE; 29 | } 30 | 31 | return lookup.get(StringUtils.trimToEmpty(styleStr).toUpperCase()); 32 | } 33 | 34 | public static boolean is(String styleStr, PathParameterStyle style) { 35 | return style == of(styleStr); 36 | } 37 | 38 | public StyleParameterDeserializer getDeserializer() { 39 | return this.deserializer; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/QueryParameterDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | import com.networknt.oas.model.Parameter; 11 | import com.networknt.oas.model.Schema; 12 | import com.networknt.openapi.OpenApiHandler; 13 | 14 | import io.undertow.server.HttpServerExchange; 15 | import io.undertow.util.AttachmentKey; 16 | 17 | public class QueryParameterDeserializer implements ParameterDeserializer{ 18 | public QueryParameterDeserializer() { 19 | } 20 | 21 | @Override 22 | public AttachmentKey> getAttachmentKey(){ 23 | return OpenApiHandler.DESERIALIZED_QUERY_PARAMETERS; 24 | } 25 | 26 | @Override 27 | public StyleParameterDeserializer getStyleDeserializer(String style) { 28 | QueryParameterStyle styleDef = QueryParameterStyle.of(style); 29 | 30 | if (null==styleDef) { 31 | return null; 32 | } 33 | 34 | return styleDef.getDeserializer(); 35 | } 36 | 37 | @Override 38 | public boolean isApplicable(HttpServerExchange exchange, Parameter parameter, Set candidateParams) { 39 | if (!candidateParams.contains(parameter.getName())) { 40 | QueryParameterStyle style = QueryParameterStyle.of(parameter.getStyle()); 41 | ValueType valueType = StyleParameterDeserializer.getValueType(parameter); 42 | 43 | return ValueType.OBJECT == valueType 44 | && parameter.isExplode() 45 | && QueryParameterStyle.FORM == style 46 | && null!=parameter.getSchema().getProperties() 47 | && parameter.getSchema().getProperties().keySet().stream().filter(prop->candidateParams.contains(prop)).findAny().isPresent(); 48 | } 49 | 50 | return true; 51 | 52 | } 53 | } 54 | 55 | class FormStyleDeserializer implements StyleParameterDeserializer{ 56 | @Override 57 | public Object deserialize(HttpServerExchange exchange, Parameter parameter, ValueType valueType, boolean exploade) { 58 | Collection values = exchange.getQueryParameters().get(parameter.getName()); 59 | 60 | if (valueType == ValueType.ARRAY) { 61 | List valueList = new ArrayList<>(); 62 | 63 | values.forEach(v->valueList.addAll(asList(v, Delimiters.COMMA))); 64 | 65 | return valueList; 66 | }else { 67 | Map valueMap = new HashMap<>(); 68 | Schema schema = parameter.getSchema(); 69 | 70 | if (exploade) { 71 | schema.getProperties().keySet().forEach(k->valueMap.put(k, getFirst(exchange.getQueryParameters().get(k), k))); 72 | }else { 73 | values.forEach(v->valueMap.putAll(asMap(v, Delimiters.COMMA))); 74 | } 75 | 76 | return valueMap; 77 | } 78 | } 79 | 80 | @Override 81 | public boolean isApplicable(ValueType valueType, boolean expload) { 82 | return (valueType == ValueType.ARRAY && !expload) || valueType == ValueType.OBJECT; 83 | } 84 | } 85 | 86 | class SpaceDelimitedStyleDeserializer implements StyleParameterDeserializer{ 87 | @Override 88 | public Object deserialize(HttpServerExchange exchange, Parameter parameter, ValueType valueType, boolean exploade) { 89 | Collection values = exchange.getQueryParameters().get(parameter.getName()); 90 | 91 | List valueList = new ArrayList<>(); 92 | 93 | values.forEach(v->valueList.addAll(asList(v, Delimiters.SPACE))); 94 | 95 | return valueList; 96 | } 97 | 98 | @Override 99 | public boolean isApplicable(ValueType valueType, boolean expload) { 100 | return (valueType == ValueType.ARRAY && !expload); 101 | } 102 | } 103 | 104 | class PipeDelimitedStyleDeserializer implements StyleParameterDeserializer{ 105 | @Override 106 | public Object deserialize(HttpServerExchange exchange, Parameter parameter, ValueType valueType, boolean exploade) { 107 | Collection values = exchange.getQueryParameters().get(parameter.getName()); 108 | 109 | List valueList = new ArrayList<>(); 110 | 111 | values.forEach(v->valueList.addAll(asList(v, Delimiters.PIPE))); 112 | 113 | return valueList; 114 | } 115 | 116 | @Override 117 | public boolean isApplicable(ValueType valueType, boolean exploade) { 118 | return (valueType == ValueType.ARRAY && !exploade); 119 | } 120 | } 121 | 122 | class DeepObjectStyleDeserializer implements StyleParameterDeserializer{ 123 | @Override 124 | public Object deserialize(HttpServerExchange exchange, Parameter parameter, ValueType valueType, boolean exploade) { 125 | Map valueMap = new HashMap<>(); 126 | Schema schema = parameter.getSchema(); 127 | schema.getProperties().keySet().forEach(k->valueMap.put(k, getFirst(exchange.getQueryParameters().get(makeKey(parameter.getName(), k)), k))); 128 | 129 | return valueMap; 130 | } 131 | 132 | @Override 133 | public boolean isApplicable(ValueType valueType, boolean exploade) { 134 | return valueType == ValueType.OBJECT && exploade; 135 | } 136 | 137 | private String makeKey(String paramName, String prop) { 138 | return String.format("%s[%s]", paramName, prop); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/QueryParameterStyle.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import com.networknt.utility.StringUtils; 7 | 8 | public enum QueryParameterStyle { 9 | FORM(new FormStyleDeserializer()), 10 | SPACEDELIMITED(new SpaceDelimitedStyleDeserializer()), 11 | PIPEDELIMITED(new PipeDelimitedStyleDeserializer()), 12 | DEEPOBJECT(new DeepObjectStyleDeserializer()); 13 | 14 | private static Map lookup = new HashMap<>(); 15 | private final StyleParameterDeserializer deserializer; 16 | 17 | private QueryParameterStyle(StyleParameterDeserializer deserializer) { 18 | this.deserializer = deserializer; 19 | } 20 | 21 | static { 22 | for (QueryParameterStyle style: QueryParameterStyle.values()) { 23 | lookup.put(style.name(), style); 24 | } 25 | } 26 | 27 | public static QueryParameterStyle of(String styleStr) { 28 | if (StringUtils.isBlank(styleStr)) { 29 | return FORM; 30 | } 31 | 32 | return lookup.get(StringUtils.trimToEmpty(styleStr).toUpperCase()); 33 | } 34 | 35 | public static boolean is(String styleStr, QueryParameterStyle style) { 36 | return style == of(styleStr); 37 | } 38 | 39 | public StyleParameterDeserializer getDeserializer() { 40 | return this.deserializer; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /openapi-meta/src/main/java/com/networknt/openapi/parameter/StyleParameterDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Deque; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import com.networknt.oas.model.Parameter; 11 | import com.networknt.oas.model.Schema; 12 | import com.networknt.utility.StringUtils; 13 | 14 | import io.undertow.server.HttpServerExchange; 15 | 16 | public interface StyleParameterDeserializer { 17 | static ValueType getValueType(Parameter parameter) { 18 | Schema schema = parameter.getSchema(); 19 | 20 | if (null!=schema) { 21 | return ValueType.of(schema.getType()); 22 | } 23 | 24 | return null; 25 | } 26 | 27 | default Object deserialize(HttpServerExchange exchange, Parameter parameter) { 28 | ValueType valueType = getValueType(parameter); 29 | boolean exploade = parameter.isExplode(); 30 | 31 | if (!isApplicable(valueType, exploade)) { 32 | return null; 33 | } 34 | 35 | return deserialize(exchange, parameter, valueType, exploade); 36 | } 37 | 38 | Object deserialize(HttpServerExchange exchange, Parameter parameter, ValueType valueType, boolean exploade); 39 | 40 | default boolean isApplicable(ValueType valueType, boolean exploade) { 41 | if (null == valueType || ValueType.PRIMITIVE == valueType) { 42 | return false; 43 | } 44 | 45 | return true; 46 | } 47 | 48 | default String getFirst(Deque values, String key) { 49 | if (null!=values) { 50 | return StringUtils.trimToEmpty(values.peekFirst()); 51 | } 52 | 53 | return StringUtils.EMPTY; 54 | } 55 | 56 | default List asList(String str, String delimiter) { 57 | List valueList = new ArrayList<>(); 58 | 59 | if (!str.contains(delimiter)) { 60 | valueList.add(str); 61 | }else { 62 | String[] items = str.split("\\"+delimiter); 63 | 64 | for (String item: items) { 65 | valueList.add(item); 66 | } 67 | } 68 | 69 | return valueList; 70 | } 71 | 72 | default Map asExploadeMap(String str, String delimiter) { 73 | Map valueMap = new HashMap<>(); 74 | String[] items = str.split("\\"+delimiter); 75 | 76 | for (String item: items) { 77 | String[] tokens = item.split(Delimiters.EQUAL); 78 | 79 | String key=null; 80 | String value=null; 81 | 82 | if (tokens.length>0) { 83 | key = tokens[0]; 84 | } 85 | 86 | if (tokens.length>1) { 87 | value = tokens[1]; 88 | } 89 | 90 | if (StringUtils.isNotBlank(key)) { 91 | valueMap.put(key, StringUtils.trimToEmpty(value)); 92 | } 93 | } 94 | 95 | return valueMap; 96 | } 97 | 98 | default Map asMap(String str, String delimiter) { 99 | if (StringUtils.isBlank(str)) { 100 | return Collections.emptyMap(); 101 | } 102 | 103 | Map valueMap = new HashMap<>(); 104 | 105 | if (!str.contains(delimiter)) { 106 | valueMap.put(str, StringUtils.EMPTY); 107 | }else { 108 | String[] items = str.split("\\"+delimiter); 109 | 110 | int len = items.length/2; 111 | 112 | int keyIndex = 0; 113 | int valueIndex = 0; 114 | 115 | for (int i=0; i lookup = new HashMap<>(); 14 | 15 | static { 16 | for (ValueType type: ValueType.values()) { 17 | lookup.put(type.name(), type); 18 | } 19 | } 20 | 21 | public static ValueType of(String typeStr) { 22 | ValueType type = lookup.get(StringUtils.trimToEmpty(typeStr).toUpperCase()); 23 | 24 | return null==type?PRIMITIVE:type; 25 | } 26 | 27 | public static boolean is(String typeStr, ValueType type) { 28 | return type == of(typeStr); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /openapi-meta/src/test/java/com/networknt/openapi/OpenApiEndpointSourceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Network New Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.networknt.openapi; 17 | 18 | import static org.junit.Assert.assertTrue; 19 | 20 | import java.util.Arrays; 21 | import java.util.HashSet; 22 | import java.util.Set; 23 | import java.util.stream.Collectors; 24 | import java.util.stream.StreamSupport; 25 | 26 | import com.networknt.config.Config; 27 | import org.junit.Assert; 28 | import org.junit.BeforeClass; 29 | import org.junit.Test; 30 | 31 | import com.networknt.handler.config.EndpointSource; 32 | 33 | public class OpenApiEndpointSourceTest { 34 | private static OpenApiHelper helper; 35 | private static final Set EXPECTED = new HashSet<>(Arrays.asList( 36 | "/v1/pets@get", 37 | "/v1/pets@post", 38 | "/v1/pets/{petId}@get", 39 | "/v1/pets/{petId}@delete" 40 | )); 41 | 42 | static { 43 | String spec = Config.getInstance().getStringFromFile("openapi.yaml"); 44 | helper = new OpenApiHelper(spec); 45 | } 46 | 47 | @Test 48 | public void testFindBasePath() { 49 | OpenApiEndpointSource source = new OpenApiEndpointSource(helper); 50 | String basePath = source.findBasePath(); 51 | Assert.assertEquals("/v1", basePath); 52 | } 53 | 54 | @Test 55 | public void testPetstoreEndpoints() { 56 | OpenApiEndpointSource source = new OpenApiEndpointSource(helper); 57 | Iterable endpoints = source.listEndpoints(); 58 | 59 | // Extract a set of string representations of endpoints 60 | Set endpointStrings = StreamSupport 61 | .stream(endpoints.spliterator(), false) 62 | .map(Object::toString) 63 | .collect(Collectors.toSet()); 64 | 65 | for (String endpoint: EXPECTED) { 66 | assertTrue(endpointStrings.contains(endpoint)); 67 | } 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /openapi-meta/src/test/java/com/networknt/openapi/parameter/ParameterDeserializerTest.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import org.junit.BeforeClass; 11 | 12 | import com.networknt.oas.model.Schema; 13 | 14 | @SuppressWarnings("rawtypes") 15 | public abstract class ParameterDeserializerTest { 16 | protected static final String PARAM_NAME="id"; 17 | protected static final String ROLE="role"; 18 | protected static final String FIRST_NAME="firstName"; 19 | protected static final String ADMIN="admin"; 20 | protected static final String ALEX="Alex"; 21 | protected static final String[] VALUES = {"3", "4", "5"}; 22 | protected static final String LAST_NAME="lastName"; 23 | protected static final Map PROPS=new HashMap<>(); 24 | 25 | @BeforeClass 26 | public static void setup() { 27 | PROPS.put("role", new PojoSchema()); 28 | PROPS.put("firstName", new PojoSchema()); 29 | PROPS.put("lastName", new PojoSchema()); 30 | } 31 | 32 | protected void checkMap(Map result, int expectedSize) { 33 | assertTrue(null!=result && !result.isEmpty()); 34 | assertTrue(result.containsKey(PARAM_NAME)); 35 | assertTrue(result.get(PARAM_NAME) instanceof Map); 36 | 37 | assertTrue(null!=result && !result.isEmpty()); 38 | assertTrue(result.containsKey(PARAM_NAME)); 39 | assertTrue(result.get(PARAM_NAME) instanceof Map); 40 | 41 | Map valueMap = ((Map)result.get(PARAM_NAME)); 42 | 43 | assertTrue(valueMap.size() == expectedSize); 44 | assertEquals(ADMIN, valueMap.get(ROLE)); 45 | assertEquals(ALEX, valueMap.get(FIRST_NAME)); 46 | } 47 | 48 | protected void checkArray(Map result) { 49 | assertTrue(null!=result && !result.isEmpty()); 50 | assertTrue(result.containsKey(PARAM_NAME)); 51 | assertTrue(result.get(PARAM_NAME) instanceof List); 52 | 53 | List valueList = ((List)result.get(PARAM_NAME)); 54 | 55 | assertTrue(valueList.size() == 3); 56 | 57 | for (String v: VALUES) { 58 | assertTrue(valueList.contains(v)); 59 | } 60 | } 61 | 62 | protected void checkString(Map result, String expectedValue) { 63 | assertTrue(null!=result && !result.isEmpty()); 64 | assertTrue(result.containsKey(PARAM_NAME)); 65 | assertTrue(result.get(PARAM_NAME) instanceof String); 66 | 67 | String value = ((String)result.get(PARAM_NAME)); 68 | 69 | assertEquals(expectedValue, value); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /openapi-meta/src/test/java/com/networknt/openapi/parameter/ParameterHandler.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import static io.undertow.util.PathTemplateMatch.ATTACHMENT_KEY; 4 | 5 | import java.util.Map; 6 | 7 | import com.networknt.handler.Handler; 8 | import com.networknt.handler.MiddlewareHandler; 9 | import com.networknt.utility.ModuleRegistry; 10 | 11 | import io.undertow.Handlers; 12 | import io.undertow.server.HttpHandler; 13 | import io.undertow.server.HttpServerExchange; 14 | import io.undertow.util.PathTemplateMatcher; 15 | 16 | /** 17 | * Simulate com.networknt.handler.Handler.start() 18 | * @author Daniel Zhao 19 | * 20 | */ 21 | public class ParameterHandler implements MiddlewareHandler { 22 | private static PathTemplateMatcher pathTemplateMatcher = new PathTemplateMatcher<>(); 23 | private volatile HttpHandler next; 24 | 25 | static { 26 | pathTemplateMatcher.add("/pets", "0"); 27 | pathTemplateMatcher.add("/pets/{petId}", "1"); 28 | pathTemplateMatcher.add("/pets_simple_array/{petId}", "2"); 29 | pathTemplateMatcher.add("/pets_simple_obj_ep/{petId}", "3"); 30 | pathTemplateMatcher.add("/pets_simple_obj_no_ep/{petId}", "4"); 31 | pathTemplateMatcher.add("/pets_label_array_ep/{petId}", "5"); 32 | pathTemplateMatcher.add("/pets_label_array_no_ep/{petId}", "6"); 33 | pathTemplateMatcher.add("/pets_label_obj_ep/{petId}", "7"); 34 | pathTemplateMatcher.add("/pets_label_obj_no_ep/{petId}", "8"); 35 | pathTemplateMatcher.add("/pets_matrix_array_ep/{petId}", "9"); 36 | pathTemplateMatcher.add("/pets_matrix_array_no_ep/{petId}", "10"); 37 | pathTemplateMatcher.add("/pets_matrix_obj_ep/{petId}", "11"); 38 | pathTemplateMatcher.add("/pets_matrix_obj_no_ep/{petId}", "12"); 39 | pathTemplateMatcher.add("/pets_matrix_pm/{petId}", "13"); 40 | } 41 | 42 | @Override 43 | public void handleRequest(HttpServerExchange exchange) throws Exception { 44 | PathTemplateMatcher.PathMatchResult result = pathTemplateMatcher.match(exchange.getRequestPath()); 45 | 46 | if (result != null) { 47 | exchange.putAttachment(ATTACHMENT_KEY, 48 | new io.undertow.util.PathTemplateMatch(result.getMatchedTemplate(), result.getParameters())); 49 | for (Map.Entry entry : result.getParameters().entrySet()) { 50 | exchange.addQueryParam(entry.getKey(), entry.getValue()); 51 | 52 | exchange.addPathParam(entry.getKey(), entry.getValue()); 53 | } 54 | } 55 | 56 | Handler.next(exchange, next); 57 | } 58 | 59 | @Override 60 | public HttpHandler getNext() { 61 | return next; 62 | } 63 | 64 | @Override 65 | public MiddlewareHandler setNext(HttpHandler next) { 66 | Handlers.handlerNotNull(next); 67 | this.next = next; 68 | return this; 69 | } 70 | 71 | @Override 72 | public boolean isEnabled() { 73 | return true; 74 | } 75 | 76 | @Override 77 | public void register() { 78 | 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /openapi-meta/src/test/java/com/networknt/openapi/parameter/QueryParameterDeserializerTest.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi.parameter; 2 | 3 | import org.junit.Test; 4 | 5 | import com.networknt.oas.model.Parameter; 6 | import com.networknt.oas.model.Schema; 7 | import com.networknt.openapi.OpenApiHandler; 8 | 9 | import io.undertow.server.HttpServerExchange; 10 | 11 | public class QueryParameterDeserializerTest extends ParameterDeserializerTest{ 12 | @Test 13 | public void test_form_array() { 14 | Schema schema = new PojoSchema(); 15 | schema.setType(ValueType.ARRAY.name().toLowerCase()); 16 | 17 | Parameter parameter = new PoJoParameter(PARAM_NAME, 18 | ParameterType.QUERY.name().toLowerCase(), 19 | QueryParameterStyle.FORM.name().toLowerCase(), 20 | false, 21 | schema); 22 | 23 | HttpServerExchange exchange = new HttpServerExchange(null); 24 | 25 | exchange.addQueryParam(PARAM_NAME, "3,4,5"); 26 | 27 | checkArray(exchange, parameter); 28 | } 29 | 30 | @Test 31 | public void test_form_object_exploade() { 32 | Schema schema = new PojoSchema(); 33 | schema.setType(ValueType.OBJECT.name().toLowerCase()); 34 | schema.setProperties(PROPS); 35 | 36 | Parameter parameter = new PoJoParameter(PARAM_NAME, 37 | ParameterType.QUERY.name().toLowerCase(), 38 | QueryParameterStyle.FORM.name().toLowerCase(), 39 | true, 40 | schema); 41 | 42 | HttpServerExchange exchange = new HttpServerExchange(null); 43 | 44 | exchange.addQueryParam(ROLE, "admin"); 45 | exchange.addQueryParam(FIRST_NAME, "Alex"); 46 | 47 | checkMap(exchange, parameter, 3); 48 | } 49 | 50 | @Test 51 | public void test_form_object_no_exploade() { 52 | Schema schema = new PojoSchema(); 53 | schema.setType(ValueType.OBJECT.name().toLowerCase()); 54 | schema.setProperties(PROPS); 55 | 56 | Parameter parameter = new PoJoParameter(PARAM_NAME, 57 | ParameterType.QUERY.name().toLowerCase(), 58 | QueryParameterStyle.FORM.name().toLowerCase(), 59 | false, 60 | schema); 61 | 62 | HttpServerExchange exchange = new HttpServerExchange(null); 63 | 64 | exchange.addQueryParam(PARAM_NAME, "role,admin,firstName,Alex"); 65 | 66 | checkMap(exchange, parameter, 2); 67 | } 68 | 69 | @Test 70 | public void test_spacedelimited_array() { 71 | Schema schema = new PojoSchema(); 72 | schema.setType(ValueType.ARRAY.name().toLowerCase()); 73 | 74 | Parameter parameter = new PoJoParameter(PARAM_NAME, 75 | ParameterType.QUERY.name().toLowerCase(), 76 | QueryParameterStyle.SPACEDELIMITED.name().toLowerCase(), 77 | false, 78 | schema); 79 | 80 | HttpServerExchange exchange = new HttpServerExchange(null); 81 | 82 | exchange.addQueryParam(PARAM_NAME, "3 4 5"); 83 | 84 | checkArray(exchange, parameter); 85 | } 86 | 87 | @Test 88 | public void test_pipedelimited_array() { 89 | Schema schema = new PojoSchema(); 90 | schema.setType(ValueType.ARRAY.name().toLowerCase()); 91 | 92 | Parameter parameter = new PoJoParameter(PARAM_NAME, 93 | ParameterType.QUERY.name().toLowerCase(), 94 | QueryParameterStyle.PIPEDELIMITED.name().toLowerCase(), 95 | false, 96 | schema); 97 | 98 | HttpServerExchange exchange = new HttpServerExchange(null); 99 | 100 | exchange.addQueryParam(PARAM_NAME, "3|4|5"); 101 | 102 | checkArray(exchange, parameter); 103 | } 104 | 105 | @Test 106 | public void test_deepobject() { 107 | Schema schema = new PojoSchema(); 108 | schema.setType(ValueType.OBJECT.name().toLowerCase()); 109 | schema.setProperties(PROPS); 110 | 111 | Parameter parameter = new PoJoParameter(PARAM_NAME, 112 | ParameterType.QUERY.name().toLowerCase(), 113 | QueryParameterStyle.DEEPOBJECT.name().toLowerCase(), 114 | true, 115 | schema); 116 | 117 | HttpServerExchange exchange = new HttpServerExchange(null); 118 | 119 | exchange.addQueryParam(String.format("%s[%s]", PARAM_NAME, ROLE), ADMIN); 120 | exchange.addQueryParam(String.format("%s[%s]", PARAM_NAME, FIRST_NAME), ALEX); 121 | 122 | checkMap(exchange, parameter, 3); 123 | } 124 | 125 | protected void checkArray(HttpServerExchange exchange, Parameter parameter) { 126 | ParameterType.QUERY.getDeserializer().deserialize(exchange, parameter, ParameterDeserializer.getCandidateQueryParams(exchange)); 127 | 128 | checkArray(exchange.getAttachment(OpenApiHandler.DESERIALIZED_QUERY_PARAMETERS)); 129 | } 130 | 131 | protected void checkMap(HttpServerExchange exchange, Parameter parameter, int expectedSize) { 132 | ParameterType.QUERY.getDeserializer().deserialize(exchange, parameter, ParameterDeserializer.getCandidateQueryParams(exchange)); 133 | checkMap(exchange.getAttachment(OpenApiHandler.DESERIALIZED_QUERY_PARAMETERS), expectedSize); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/client.truststore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networknt/light-rest-4j/27c19bbaf3ad3792007aa89dc6b8f018c52178c0/openapi-meta/src/test/resources/config/client.truststore -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/handler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | enabled: true 3 | 4 | # Configuration for the LightHttpHandler. The handler is the base class for all middleware, server and health handlers 5 | # set the Status Object in the AUDIT_INFO, for auditing purposes 6 | # default, if not set:false 7 | auditOnError: true 8 | 9 | # set the StackTrace in the AUDIT_INFO, for auditing purposes 10 | # default, if not set:false 11 | auditStackTrace: true 12 | 13 | handlers: 14 | - com.networknt.handler.sample.SampleHttpHandler1 15 | - com.networknt.handler.sample.SampleHttpHandler2 16 | - com.networknt.handler.sample.SampleHttpHandler3@third 17 | 18 | chains: 19 | secondBeforeFirst: 20 | - com.networknt.handler.sample.SampleHttpHandler2 21 | - com.networknt.handler.sample.SampleHttpHandler1 22 | 23 | paths: 24 | - path: '/test' 25 | method: 'get' 26 | exec: 27 | - secondBeforeFirst 28 | - third 29 | - path: '/v2/health' 30 | method: 'post' 31 | exec: 32 | - secondBeforeFirst 33 | - third 34 | # If there is no matched path, then it goes here first. If this is not set, then an error 35 | # will be returned. 36 | defaultHandlers: 37 | - third 38 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/openapi-handler-multiple.yml: -------------------------------------------------------------------------------- 1 | # openapi-handler.yml 2 | # This configuration file is used to support multiple OpenAPI specifications in the same light-rest-4j instance. 3 | # An indicator to allow multiple openapi specifications. Default to false which only allow one spec named openapi.yml or openapi.yaml or openapi.json. 4 | multipleSpec: ${openapi-handler.multipleSpec:true} 5 | # When the OpenApiHandler is used in a shared gateway and some backend APIs have no specifications deployed on the gateway, the handler will return 6 | # an invalid request path error to the client. To allow the call to pass through the OpenApiHandler and route to the backend APIs, you can set this 7 | # flag to true. In this mode, the handler will only add the endpoint specification to the auditInfo if it can find it. Otherwise, it will pass through. 8 | ignoreInvalidPath: ${openapi-handler.ignoreInvalidPath:false} 9 | # Path to spec mapping. One or more base paths can map to the same specifications. The key is the base path and the value is the specification name. 10 | # If users want to use multiple specification files in the same instance, each specification must have a unique base path and it must be set as key. 11 | pathSpecMapping: 12 | /petstore: openapi-petstore 13 | /market: openapi-market 14 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/openapi-inject-test-dup-method.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | paths: 3 | /pets/{petId}: 4 | get: 5 | security: 6 | - api-scope: 7 | - admim 8 | 9 | components: 10 | securitySchemes: 11 | api-scope: 12 | flows: 13 | clientCredentials: 14 | scopes: 15 | admin: orwritten 16 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/openapi-inject-test-dup-path.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | paths: 3 | /pets/{petId}: 4 | # the original spec does not contain put method 5 | put: 6 | security: 7 | - api-scope: 8 | - admim 9 | 10 | components: 11 | securitySchemes: 12 | api-scope: 13 | flows: 14 | clientCredentials: 15 | scopes: 16 | admin: orwritten 17 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/openapi-inject-test-neg.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | paths: 3 | /pets/${badKey}: 4 | get: 5 | security: 6 | - api-scope: 7 | - admim 8 | 9 | components: 10 | securitySchemes: 11 | api-scope: 12 | flows: 13 | clientCredentials: 14 | scopes: 15 | admin: orwritten 16 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/openapi-inject-test-pos.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | paths: 3 | /pets/${goodKey}: 4 | get: 5 | security: 6 | - api-scope: 7 | - admim 8 | 9 | components: 10 | securitySchemes: 11 | api-scope: 12 | flows: 13 | clientCredentials: 14 | scopes: 15 | admin: orwritten 16 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/openapi-market.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | version: 1.0.0 4 | title: Swagger Market 5 | license: 6 | name: MIT 7 | servers: 8 | - url: 'http://market.swagger.io/market' 9 | paths: 10 | /{store}/products: 11 | get: 12 | summary: Get all products from stores 13 | operationId: listProducts 14 | tags: 15 | - products 16 | parameters: 17 | - name: limit 18 | in: query 19 | description: How many items to return at one time (max 100) 20 | required: false 21 | schema: 22 | type: integer 23 | format: int32 24 | - name: store 25 | in: path 26 | description: The downstream store name 27 | required: true 28 | schema: 29 | type: string 30 | security: 31 | - market_auth: 32 | - 'read:products' 33 | responses: 34 | '200': 35 | description: An paged array of products 36 | content: 37 | application/json: 38 | schema: 39 | type: array 40 | items: 41 | $ref: '#/components/schemas/Product' 42 | example: 43 | - id: 1 44 | name: catten 45 | tag: cat 46 | - id: 2 47 | name: doggy 48 | tag: dog 49 | default: 50 | description: unexpected error 51 | content: 52 | application/json: 53 | schema: 54 | $ref: '#/components/schemas/Error' 55 | post: 56 | summary: Create a product 57 | operationId: createProducts 58 | parameters: 59 | - name: store 60 | in: path 61 | description: The downstream store name 62 | required: true 63 | schema: 64 | type: string 65 | requestBody: 66 | description: Product to add to the target store 67 | required: true 68 | content: 69 | application/json: 70 | schema: 71 | $ref: '#/components/schemas/Product' 72 | tags: 73 | - products 74 | security: 75 | - market_auth: 76 | - 'read:products' 77 | - 'write:products' 78 | responses: 79 | '201': 80 | description: Null response 81 | default: 82 | description: unexpected error 83 | content: 84 | application/json: 85 | schema: 86 | $ref: '#/components/schemas/Error' 87 | components: 88 | securitySchemes: 89 | market_auth: 90 | type: oauth2 91 | description: This API uses OAuth 2 with the client credential grant flow. 92 | flows: 93 | clientCredentials: 94 | tokenUrl: 'https://localhost:6882/token' 95 | scopes: 96 | 'write:products': modify products 97 | 'read:products': read your products 98 | schemas: 99 | Product: 100 | type: object 101 | required: 102 | - id 103 | - name 104 | properties: 105 | id: 106 | type: integer 107 | format: int64 108 | name: 109 | type: string 110 | tag: 111 | type: string 112 | Error: 113 | type: object 114 | required: 115 | - code 116 | - message 117 | properties: 118 | code: 119 | type: integer 120 | format: int32 121 | message: 122 | type: string 123 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/openapi-petstore.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | version: 1.0.0 4 | title: Swagger Petstore 5 | license: 6 | name: MIT 7 | servers: 8 | - url: 'http://petstore.swagger.io/petstore' 9 | paths: 10 | /pets: 11 | get: 12 | summary: List all pets 13 | operationId: listPets 14 | tags: 15 | - pets 16 | parameters: 17 | - name: limit 18 | in: query 19 | description: How many items to return at one time (max 100) 20 | required: false 21 | schema: 22 | type: integer 23 | format: int32 24 | security: 25 | - petstore_auth: 26 | - 'read:pets' 27 | responses: 28 | '200': 29 | description: An paged array of pets 30 | headers: 31 | x-next: 32 | description: A link to the next page of responses 33 | schema: 34 | type: string 35 | content: 36 | application/json: 37 | schema: 38 | type: array 39 | items: 40 | $ref: '#/components/schemas/Pet' 41 | example: 42 | - id: 1 43 | name: catten 44 | tag: cat 45 | - id: 2 46 | name: doggy 47 | tag: dog 48 | default: 49 | description: unexpected error 50 | content: 51 | application/json: 52 | schema: 53 | $ref: '#/components/schemas/Error' 54 | post: 55 | summary: Create a pet 56 | operationId: createPets 57 | requestBody: 58 | description: Pet to add to the store 59 | required: true 60 | content: 61 | application/json: 62 | schema: 63 | $ref: '#/components/schemas/Pet' 64 | tags: 65 | - pets 66 | security: 67 | - petstore_auth: 68 | - 'read:pets' 69 | - 'write:pets' 70 | responses: 71 | '201': 72 | description: Null response 73 | default: 74 | description: unexpected error 75 | content: 76 | application/json: 77 | schema: 78 | $ref: '#/components/schemas/Error' 79 | '/pets/{petId}': 80 | get: 81 | summary: Info for a specific pet 82 | operationId: showPetById 83 | tags: 84 | - pets 85 | parameters: 86 | - name: petId 87 | in: path 88 | required: true 89 | description: The id of the pet to retrieve 90 | schema: 91 | type: string 92 | security: 93 | - petstore_auth: 94 | - 'read:pets' 95 | responses: 96 | '200': 97 | description: Expected response to a valid request 98 | content: 99 | application/json: 100 | schema: 101 | $ref: '#/components/schemas/Pet' 102 | example: 103 | id: 1 104 | name: Jessica Right 105 | tag: pet 106 | default: 107 | description: unexpected error 108 | content: 109 | application/json: 110 | schema: 111 | $ref: '#/components/schemas/Error' 112 | delete: 113 | summary: Delete a specific pet 114 | operationId: deletePetById 115 | tags: 116 | - pets 117 | parameters: 118 | - name: petId 119 | in: path 120 | required: true 121 | description: The id of the pet to delete 122 | schema: 123 | type: string 124 | - name: key 125 | in: header 126 | required: true 127 | description: The key header 128 | schema: 129 | type: string 130 | security: 131 | - petstore_auth: 132 | - 'write:pets' 133 | responses: 134 | '200': 135 | description: Expected response to a valid request 136 | content: 137 | application/json: 138 | schema: 139 | $ref: '#/components/schemas/Pet' 140 | examples: 141 | response: 142 | value: 143 | id: 1 144 | name: Jessica Right 145 | tag: pet 146 | default: 147 | description: unexpected error 148 | content: 149 | application/json: 150 | schema: 151 | $ref: '#/components/schemas/Error' 152 | /notifications: 153 | get: 154 | summary: Get Notifications 155 | operationId: listNotifications 156 | tags: 157 | - notifications 158 | security: 159 | - petstore_auth: 160 | - 'read:pets' 161 | responses: 162 | '200': 163 | description: A standard notification response in JSON for response interceptor test 164 | 165 | /flowers: 166 | post: 167 | summary: The API accept XML and the consumer is using JSON 168 | operationId: flowers 169 | tags: 170 | - flowers 171 | security: 172 | - petstore_auth: 173 | - 'read:pets' 174 | responses: 175 | '200': 176 | description: Return an flowers XML as the demo soap service 177 | 178 | components: 179 | securitySchemes: 180 | petstore_auth: 181 | type: oauth2 182 | description: This API uses OAuth 2 with the client credential grant flow. 183 | flows: 184 | clientCredentials: 185 | tokenUrl: 'https://localhost:6882/token' 186 | scopes: 187 | 'write:pets': modify pets in your account 188 | 'read:pets': read your pets 189 | schemas: 190 | Pet: 191 | type: object 192 | required: 193 | - id 194 | - name 195 | properties: 196 | id: 197 | type: integer 198 | format: int64 199 | name: 200 | type: string 201 | tag: 202 | type: string 203 | Error: 204 | type: object 205 | required: 206 | - code 207 | - message 208 | properties: 209 | code: 210 | type: integer 211 | format: int32 212 | message: 213 | type: string 214 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/config/values.yml: -------------------------------------------------------------------------------- 1 | # server.yml 2 | server.serviceId: com.networknt.petstore-1.0.0 3 | badKey: "{petId}" 4 | goodKey: petId 5 | -------------------------------------------------------------------------------- /openapi-meta/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | TODO create logger for audit only. 20 | http://stackoverflow.com/questions/2488558/logback-to-log-different-messages-to-two-files 21 | 22 | PROFILER 23 | 24 | NEUTRAL 25 | 26 | 27 | 28 | 30 | 31 | %d{HH:mm:ss.SSS} [%thread] %-5marker %-5level %logger{36} - %msg%n 32 | 33 | 34 | 35 | 36 | target/test.log 37 | false 38 | 39 | %d{HH:mm:ss.SSS} [%thread] %-5level %class{36}:%L %M - %msg%n 40 | 41 | 42 | 43 | 44 | 45 | target/audit.log 46 | 47 | %-5level [%thread] %date{ISO8601} %F:%L - %msg%n 48 | true 49 | 50 | 51 | target/audit.log.%i.zip 52 | 1 53 | 5 54 | 55 | 56 | 200MB 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /openapi-security/pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 4.0.0 20 | 21 | 22 | com.networknt 23 | light-rest-4j 24 | 2.2.3-SNAPSHOT 25 | ../pom.xml 26 | 27 | 28 | openapi-security 29 | jar 30 | openapi-security 31 | An OpenAPI Specification 3.0 security module that contains all handlers to protect your server 32 | 33 | 34 | 35 | com.networknt 36 | http-string 37 | 38 | 39 | com.networknt 40 | config 41 | 42 | 43 | com.networknt 44 | client 45 | 46 | 47 | com.networknt 48 | utility 49 | 50 | 51 | com.networknt 52 | security 53 | 54 | 55 | com.networknt 56 | unified-config 57 | 58 | 59 | com.networknt 60 | unified-security 61 | ${version.light-4j} 62 | 63 | 64 | com.networknt 65 | handler 66 | 67 | 68 | com.networknt 69 | status 70 | 71 | 72 | com.networknt 73 | basic-auth 74 | 75 | 76 | com.networknt 77 | api-key 78 | 79 | 80 | com.networknt 81 | server 82 | 83 | 84 | com.networknt 85 | json-overlay 86 | 87 | 88 | com.networknt 89 | openapi-parser 90 | 91 | 92 | com.networknt 93 | openapi-meta 94 | 95 | 96 | io.undertow 97 | undertow-core 98 | 99 | 100 | com.fasterxml.jackson.core 101 | jackson-databind 102 | 103 | 104 | org.slf4j 105 | slf4j-api 106 | 107 | 108 | org.owasp.encoder 109 | encoder 110 | 111 | 112 | org.bitbucket.b_c 113 | jose4j 114 | 115 | 116 | 117 | ch.qos.logback 118 | logback-classic 119 | test 120 | 121 | 122 | junit 123 | junit 124 | test 125 | 126 | 127 | org.apache.commons 128 | commons-text 129 | test 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /openapi-security/src/main/java/com/networknt/openapi/SimpleJwtVerifyHandler.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi; 2 | 3 | import com.networknt.config.Config; 4 | import com.networknt.handler.Handler; 5 | import com.networknt.handler.MiddlewareHandler; 6 | import com.networknt.handler.config.HandlerConfig; 7 | import com.networknt.security.*; 8 | import com.networknt.utility.ModuleRegistry; 9 | import io.undertow.Handlers; 10 | import io.undertow.server.HttpHandler; 11 | import io.undertow.server.HttpServerExchange; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * This is very simple jwt verify handler that is used to verify jwt token without scopes. Other than scopes, it is 17 | * the same as the normal JwtVerifyHandler. 18 | * 19 | * @author Steve Hu 20 | */ 21 | public class SimpleJwtVerifyHandler extends AbstractSimpleJwtVerifyHandler { 22 | static final Logger logger = LoggerFactory.getLogger(SimpleJwtVerifyHandler.class); 23 | 24 | String basePath; 25 | 26 | public SimpleJwtVerifyHandler() { 27 | // at this moment, we assume that the OpenApiHandler is fully loaded with a single spec or multiple specs. 28 | // And the basePath is the correct one from the OpenApiHandler helper or helperMap if multiple is used. 29 | config = SecurityConfig.load(); 30 | jwtVerifier = new JwtVerifier(config); 31 | // in case that the specification doesn't exist, get the basePath from the handler.yml for endpoint lookup. 32 | HandlerConfig handlerConfig = HandlerConfig.load(); 33 | this.basePath = handlerConfig == null ? "/" : handlerConfig.getBasePath(); 34 | } 35 | 36 | @Override 37 | public HttpHandler getNext() { 38 | return next; 39 | } 40 | 41 | @Override 42 | public MiddlewareHandler setNext(final HttpHandler next) { 43 | Handlers.handlerNotNull(next); 44 | this.next = next; 45 | return this; 46 | } 47 | 48 | @Override 49 | public boolean isEnabled() { 50 | return config.isEnableVerifyJwt(); 51 | } 52 | 53 | @Override 54 | public void register() { 55 | ModuleRegistry.registerModule(SecurityConfig.CONFIG_NAME, SimpleJwtVerifyHandler.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(SecurityConfig.CONFIG_NAME), null); 56 | } 57 | 58 | @Override 59 | public void reload() { 60 | config.reload(); 61 | jwtVerifier = new JwtVerifier(config); 62 | ModuleRegistry.registerModule(SecurityConfig.CONFIG_NAME, SimpleJwtVerifyHandler.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(SecurityConfig.CONFIG_NAME), null); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /openapi-security/src/main/resources/config/primary.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDmzCCAoOgAwIBAgIEHnAgtDANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJDQTEQMA4GA1UE 3 | CBMHT250YXJpbzEUMBIGA1UEBxMLTWlzc2lzc2F1Z2ExJjAkBgNVBAoTHU5ldHdvcmsgTmV3IFRl 4 | Y2hub2xvZ2llcyBJbmMuMQwwCgYDVQQLEwNERVYxETAPBgNVBAMTCFN0ZXZlIEh1MB4XDTE2MDkw 5 | MTE2MTYxNVoXDTI2MDcxMTE2MTYxNVowfjELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8x 6 | FDASBgNVBAcTC01pc3Npc3NhdWdhMSYwJAYDVQQKEx1OZXR3b3JrIE5ldyBUZWNobm9sb2dpZXMg 7 | SW5jLjEMMAoGA1UECxMDREVWMREwDwYDVQQDEwhTdGV2ZSBIdTCCASIwDQYJKoZIhvcNAQEBBQAD 8 | ggEPADCCAQoCggEBALrlxMtDb60DogElf4TBz504tRheZimAE0dJL/Yby4nacJdqvc5l4z+WWpDf 9 | rI9krQ2Yi9yvhwAP+PrR6gWcIqWP4cpNE7XIAUDgr4CtyI7CptT/lpjtbkz4DGCMmaeDn0jqHqJt 10 | SeSZGfwVu5zAGm8n4sHatjnnxBI/iWzkTII3V4xv0WeK37szNTEd+ly2ag7n2IV5zNnYmqZTeMQm 11 | J2ENS+IwAG3ENtiVtrVTx/2bGtqutJjtdxsN58/cUG/guRyMT6OPI8Yi3ZzevdvRbxadyhEl/Kaw 12 | 6vJcdxmJI3tp4lx+p6sAxOWa7aapJe4JxutAQqzv0GKdVjoHKQ1wB60CAwEAAaMhMB8wHQYDVR0O 13 | BBYEFIPF9SBd06RWU1eDL73CKfy01lavMA0GCSqGSIb3DQEBCwUAA4IBAQAoaKZGOak3Upz/ordF 14 | slZoJuZlCu7jnKQEjYwHf3DNxcd1WmgFPtMcna6pW0VUxPIfidEA6VCMsGoK1RvshB0SjrRdCht6 15 | 5qPXs9kV3NW0WvMiwDSYZZ9HgaZ9efTe5E9Fzc7ltKrE43L6k8NJcaEEWEdpdjFbrAqH4I+j/Vro 16 | K3OhIo062fXjas5ipL4gF+3ECImjWzirQP8UiAfM0/36x7rtAu3btH/qI9hSyx39LBPPE5AsDJZ4 17 | dSMwNTW1gqmBAZIj+zQ/RD5dyWfPwON7Q+t96YbK6WBuYo0xy+I+PjcUgrWYWP3N24hlq8ZBIei+ 18 | BudoEVJlIlmS0aRCuP8n 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /openapi-security/src/main/resources/config/secondary.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDkzCCAnugAwIBAgIEUBGbJDANBgkqhkiG9w0BAQsFADB6MQswCQYDVQQGEwJDQTEQMA4GA1UE 3 | CBMHT250YXJpbzEQMA4GA1UEBxMHVG9yb250bzEmMCQGA1UEChMdTmV0d29yayBOZXcgVGVjaG5v 4 | bG9naWVzIEluYy4xDDAKBgNVBAsTA0FQSTERMA8GA1UEAxMIU3RldmUgSHUwHhcNMTYwOTIyMjI1 5 | OTIxWhcNMjYwODAxMjI1OTIxWjB6MQswCQYDVQQGEwJDQTEQMA4GA1UECBMHT250YXJpbzEQMA4G 6 | A1UEBxMHVG9yb250bzEmMCQGA1UEChMdTmV0d29yayBOZXcgVGVjaG5vbG9naWVzIEluYy4xDDAK 7 | BgNVBAsTA0FQSTERMA8GA1UEAxMIU3RldmUgSHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 8 | AoIBAQCqYfarFwug2DwpG/mmcW77OluaHVNsKEVJ/BptLp5suJAH/Z70SS5pwM4x2QwMOVO2ke8U 9 | rsAws8allxcuKXrbpVt4evpO1Ly2sFwqB1bjN3+VMp6wcT+tSjzYdVGFpQAYHpeA+OLuoHtQyfpB 10 | 0KCveTEe3KAG33zXDNfGKTGmupZ3ZfmBLINoey/X13rY71ITt67AY78VHUKb+D53MBahCcjJ9YpJ 11 | UHG+Sd3d4oeXiQcqJCBCVpD97awWARf8WYRIgU1xfCe06wQ3CzH3+GyfozLeu76Ni5PwE1tm7Dhg 12 | EDSSZo5khmzVzo4G0T2sOeshePc5weZBNRHdHlJA0L0fAgMBAAGjITAfMB0GA1UdDgQWBBT9rnek 13 | spnrFus5wTszjdzYgKll9TANBgkqhkiG9w0BAQsFAAOCAQEAT8udTfUGBgeWbN6ZAXRI64VsSJj5 14 | 1sNUN1GPDADLxZF6jArKU7LjBNXn9bG5VjJqlx8hQ1SNvi/t7FqBRCUt/3MxDmGZrVZqLY1kZ2e7 15 | x+5RykbspA8neEUtU8sOr/NP3O5jBjU77EVec9hNNT5zwKLevZNL/Q5mfHoc4GrIAolQvi/5fEqC 16 | 8OMdOIWS6sERgjaeI4tXxQtHDcMo5PeLW0/7t5sgEsadZ+pkdeEMVTmLfgf97bpNNI7KF5uEbYnQ 17 | NpwCT+NNC5ACmJmKidrfW23kml1C7vr7YzTevw9QuH/hN8l/Rh0fr+iPEVpgN6Zv00ymoKGmjuuW 18 | owVmdKg/0w== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /openapi-security/src/test/java/com/networknt/openapi/TestServer.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi; 2 | 3 | import com.networknt.server.Server; 4 | import com.networknt.server.ServerConfig; 5 | import org.junit.rules.ExternalResource; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | public class TestServer extends ExternalResource { 12 | static final Logger logger = LoggerFactory.getLogger(TestServer.class); 13 | 14 | private static final AtomicInteger refCount = new AtomicInteger(0); 15 | private static Server server; 16 | 17 | private static final TestServer instance = new TestServer(); 18 | 19 | public static TestServer getInstance () { 20 | return instance; 21 | } 22 | 23 | private TestServer() { 24 | 25 | } 26 | 27 | public ServerConfig getServerConfig() { 28 | return ServerConfig.getInstance(); 29 | } 30 | 31 | @Override 32 | protected void before() { 33 | try { 34 | if (refCount.get() == 0) { 35 | Server.start(); 36 | } 37 | } 38 | finally { 39 | refCount.getAndIncrement(); 40 | } 41 | } 42 | 43 | @Override 44 | protected void after() { 45 | refCount.getAndDecrement(); 46 | if (refCount.get() == 0) { 47 | Server.stop(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /openapi-security/src/test/resources/config/client.truststore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networknt/light-rest-4j/27c19bbaf3ad3792007aa89dc6b8f018c52178c0/openapi-security/src/test/resources/config/client.truststore -------------------------------------------------------------------------------- /openapi-security/src/test/resources/config/handler.yml: -------------------------------------------------------------------------------- 1 | # Handler middleware chain configuration 2 | --- 3 | enabled: true 4 | 5 | # Configuration for the LightHttpHandler. The handler is the base class for all middleware, server and health handlers 6 | # set the Status Object in the AUDIT_INFO, for auditing purposes 7 | # default, if not set:false 8 | auditOnError: ${handler.auditOnError:false} 9 | 10 | # set the StackTrace in the AUDIT_INFO, for auditing purposes 11 | # default, if not set:false 12 | auditStackTrace: ${handler.auditStackTrace:false} 13 | 14 | # Base Path of the API endpoints 15 | basePath: ${handler.basePath:/} 16 | 17 | #------------------------------------------------------------------------------ 18 | # Support individual handler chains for each separate endpoint. It allows framework 19 | # handlers like health check, server info to bypass majority of the middleware handlers 20 | # and allows mixing multiple frameworks like OpenAPI and GraphQL in the same instance. 21 | # 22 | # handlers -- list of handlers to be used across chains in this microservice 23 | # including the routing handlers for ALL endpoints 24 | # -- format: fully qualified handler class name@optional:given name 25 | # chains -- allows forming of [1..N] chains, which could be wholly or 26 | # used to form handler chains for each endpoint 27 | # ex.: default chain below, reused partially across multiple endpoints 28 | # paths -- list all the paths to be used for routing within the microservice 29 | # ---- path: the URI for the endpoint (ex.: path: '/v1/pets') 30 | # ---- method: the operation in use (ex.: 'post') 31 | # ---- exec: handlers to be executed -- this element forms the list and 32 | # the order of execution for the handlers 33 | # 34 | # IMPORTANT NOTES: 35 | # - to avoid executing a handler, it has to be removed/commented out in the chain 36 | # or change the enabled:boolean to false for a middleware handler configuration. 37 | # - all handlers, routing handler included, are to be listed in the execution chain 38 | # - for consistency, give a name to each handler; it is easier to refer to a name 39 | # vs a fully qualified class name and is more elegant 40 | # - you can list in chains the fully qualified handler class names, and avoid using the 41 | # handlers element altogether 42 | #------------------------------------------------------------------------------ 43 | handlers: ${handler.handlers:} 44 | 45 | chains: 46 | default: ${handler.chains.default:} 47 | 48 | paths: 49 | - path: '/*' 50 | method: 'GET' 51 | exec: 52 | - default 53 | - path: '/*' 54 | method: 'POST' 55 | exec: 56 | - default 57 | - path: '/*' 58 | method: 'PUT' 59 | exec: 60 | - default 61 | - path: '/*' 62 | method: 'DELETE' 63 | exec: 64 | - default 65 | - path: '/*' 66 | method: 'PATCH' 67 | exec: 68 | - default 69 | -------------------------------------------------------------------------------- /openapi-security/src/test/resources/config/openapi-handler-multiple.yml: -------------------------------------------------------------------------------- 1 | # openapi-handler.yml 2 | # This configuration file is used to support multiple OpenAPI specifications in the same light-rest-4j instance. 3 | # An indicator to allow multiple openapi specifications. Default to false which only allow one spec named openapi.yml or openapi.yaml or openapi.json. 4 | multipleSpec: ${openapi-handler.multipleSpec:true} 5 | # Path to spec mapping. One or more base paths can map to the same specifications. The key is the base path and the value is the specification name. 6 | # If users want to use multiple specification files in the same instance, each specification must have a unique base path and it must be set as key. 7 | pathSpecMapping: 8 | /petstore: openapi-petstore 9 | /market: openapi-market 10 | -------------------------------------------------------------------------------- /openapi-security/src/test/resources/config/openapi-market.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | version: 1.0.0 4 | title: Swagger Market 5 | license: 6 | name: MIT 7 | servers: 8 | - url: 'http://market.swagger.io/market' 9 | paths: 10 | /{store}/products: 11 | get: 12 | summary: Get all products from stores 13 | operationId: listProducts 14 | tags: 15 | - products 16 | parameters: 17 | - name: limit 18 | in: query 19 | description: How many items to return at one time (max 100) 20 | required: false 21 | schema: 22 | type: integer 23 | format: int32 24 | - name: store 25 | in: path 26 | description: The downstream store name 27 | required: true 28 | schema: 29 | type: string 30 | security: 31 | - market_auth: 32 | - 'read:products' 33 | responses: 34 | '200': 35 | description: An paged array of products 36 | content: 37 | application/json: 38 | schema: 39 | type: array 40 | items: 41 | $ref: '#/components/schemas/Product' 42 | example: 43 | - id: 1 44 | name: catten 45 | tag: cat 46 | - id: 2 47 | name: doggy 48 | tag: dog 49 | default: 50 | description: unexpected error 51 | content: 52 | application/json: 53 | schema: 54 | $ref: '#/components/schemas/Error' 55 | post: 56 | summary: Create a product 57 | operationId: createProducts 58 | parameters: 59 | - name: store 60 | in: path 61 | description: The downstream store name 62 | required: true 63 | schema: 64 | type: string 65 | requestBody: 66 | description: Product to add to the target store 67 | required: true 68 | content: 69 | application/json: 70 | schema: 71 | $ref: '#/components/schemas/Product' 72 | tags: 73 | - products 74 | security: 75 | - market_auth: 76 | - 'read:products' 77 | - 'write:products' 78 | responses: 79 | '201': 80 | description: Null response 81 | default: 82 | description: unexpected error 83 | content: 84 | application/json: 85 | schema: 86 | $ref: '#/components/schemas/Error' 87 | components: 88 | securitySchemes: 89 | market_auth: 90 | type: oauth2 91 | description: This API uses OAuth 2 with the client credential grant flow. 92 | flows: 93 | clientCredentials: 94 | tokenUrl: 'https://localhost:6882/token' 95 | scopes: 96 | 'write:products': modify products 97 | 'read:products': read your products 98 | schemas: 99 | Product: 100 | type: object 101 | required: 102 | - id 103 | - name 104 | properties: 105 | id: 106 | type: integer 107 | format: int64 108 | name: 109 | type: string 110 | tag: 111 | type: string 112 | Error: 113 | type: object 114 | required: 115 | - code 116 | - message 117 | properties: 118 | code: 119 | type: integer 120 | format: int32 121 | message: 122 | type: string 123 | -------------------------------------------------------------------------------- /openapi-security/src/test/resources/config/openapi-petstore.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | version: 1.0.0 4 | title: Swagger Petstore 5 | license: 6 | name: MIT 7 | servers: 8 | - url: 'http://petstore.swagger.io/petstore' 9 | paths: 10 | /pets: 11 | get: 12 | summary: List all pets 13 | operationId: listPets 14 | tags: 15 | - pets 16 | parameters: 17 | - name: limit 18 | in: query 19 | description: How many items to return at one time (max 100) 20 | required: false 21 | schema: 22 | type: integer 23 | format: int32 24 | security: 25 | - petstore_auth: 26 | - 'read:pets' 27 | responses: 28 | '200': 29 | description: An paged array of pets 30 | headers: 31 | x-next: 32 | description: A link to the next page of responses 33 | schema: 34 | type: string 35 | content: 36 | application/json: 37 | schema: 38 | type: array 39 | items: 40 | $ref: '#/components/schemas/Pet' 41 | example: 42 | - id: 1 43 | name: catten 44 | tag: cat 45 | - id: 2 46 | name: doggy 47 | tag: dog 48 | default: 49 | description: unexpected error 50 | content: 51 | application/json: 52 | schema: 53 | $ref: '#/components/schemas/Error' 54 | post: 55 | summary: Create a pet 56 | operationId: createPets 57 | requestBody: 58 | description: Pet to add to the store 59 | required: true 60 | content: 61 | application/json: 62 | schema: 63 | $ref: '#/components/schemas/Pet' 64 | tags: 65 | - pets 66 | security: 67 | - petstore_auth: 68 | - 'read:pets' 69 | - 'write:pets' 70 | responses: 71 | '201': 72 | description: Null response 73 | default: 74 | description: unexpected error 75 | content: 76 | application/json: 77 | schema: 78 | $ref: '#/components/schemas/Error' 79 | '/pets/{petId}': 80 | get: 81 | summary: Info for a specific pet 82 | operationId: showPetById 83 | tags: 84 | - pets 85 | parameters: 86 | - name: petId 87 | in: path 88 | required: true 89 | description: The id of the pet to retrieve 90 | schema: 91 | type: string 92 | security: 93 | - petstore_auth: 94 | - 'read:pets' 95 | responses: 96 | '200': 97 | description: Expected response to a valid request 98 | content: 99 | application/json: 100 | schema: 101 | $ref: '#/components/schemas/Pet' 102 | example: 103 | id: 1 104 | name: Jessica Right 105 | tag: pet 106 | default: 107 | description: unexpected error 108 | content: 109 | application/json: 110 | schema: 111 | $ref: '#/components/schemas/Error' 112 | delete: 113 | summary: Delete a specific pet 114 | operationId: deletePetById 115 | tags: 116 | - pets 117 | parameters: 118 | - name: petId 119 | in: path 120 | required: true 121 | description: The id of the pet to delete 122 | schema: 123 | type: string 124 | - name: key 125 | in: header 126 | required: true 127 | description: The key header 128 | schema: 129 | type: string 130 | security: 131 | - petstore_auth: 132 | - 'write:pets' 133 | responses: 134 | '200': 135 | description: Expected response to a valid request 136 | content: 137 | application/json: 138 | schema: 139 | $ref: '#/components/schemas/Pet' 140 | examples: 141 | response: 142 | value: 143 | id: 1 144 | name: Jessica Right 145 | tag: pet 146 | default: 147 | description: unexpected error 148 | content: 149 | application/json: 150 | schema: 151 | $ref: '#/components/schemas/Error' 152 | /notifications: 153 | get: 154 | summary: Get Notifications 155 | operationId: listNotifications 156 | tags: 157 | - notifications 158 | security: 159 | - petstore_auth: 160 | - 'read:pets' 161 | responses: 162 | '200': 163 | description: A standard notification response in JSON for response interceptor test 164 | 165 | /flowers: 166 | post: 167 | summary: The API accept XML and the consumer is using JSON 168 | operationId: flowers 169 | tags: 170 | - flowers 171 | security: 172 | - petstore_auth: 173 | - 'read:pets' 174 | responses: 175 | '200': 176 | description: Return an flowers XML as the demo soap service 177 | 178 | components: 179 | securitySchemes: 180 | petstore_auth: 181 | type: oauth2 182 | description: This API uses OAuth 2 with the client credential grant flow. 183 | flows: 184 | clientCredentials: 185 | tokenUrl: 'https://localhost:6882/token' 186 | scopes: 187 | 'write:pets': modify pets in your account 188 | 'read:pets': read your pets 189 | schemas: 190 | Pet: 191 | type: object 192 | required: 193 | - id 194 | - name 195 | properties: 196 | id: 197 | type: integer 198 | format: int64 199 | name: 200 | type: string 201 | tag: 202 | type: string 203 | Error: 204 | type: object 205 | required: 206 | - code 207 | - message 208 | properties: 209 | code: 210 | type: integer 211 | format: int32 212 | message: 213 | type: string 214 | -------------------------------------------------------------------------------- /openapi-security/src/test/resources/config/unified-security.yml: -------------------------------------------------------------------------------- 1 | # unified-security.yml 2 | # indicate if this handler is enabled. By default, it will be enabled if it is injected into the 3 | # request/response chain in the handler.yml configuration. 4 | enabled: ${unified-security.enabled:true} 5 | # Anonymous prefixes configuration. A list of request path prefixes. The anonymous prefixes will be checked 6 | # first, and if any path is matched, all other security checks will be bypassed, and the request goes to 7 | # the next handler in the chain. You can use json array or string separated by comma or YAML format. 8 | anonymousPrefixes: 9 | - /v1/cats 10 | - /v1/dogs 11 | - /oauth2 12 | 13 | pathPrefixAuths: 14 | - prefix: /v1/salesforce 15 | basic: true 16 | jwt: true 17 | apikey: true 18 | jwkServiceIds: com.networknt.petstore-1.0.0, com.networknt.market-1.0.0 19 | - prefix: /v1/blackrock 20 | basic: true 21 | jwt: true 22 | jwkServiceIds: ["com.networknt.petstore-1.0.0", "com.networknt.market-1.0.0"] 23 | - prefix: /v1/test1 24 | apikey: true 25 | - prefix: /v1/pets 26 | jwt: true 27 | -------------------------------------------------------------------------------- /openapi-security/src/test/resources/config/values.yml: -------------------------------------------------------------------------------- 1 | # server.yml 2 | server.enableHttps: false 3 | server.enableHttp2: false 4 | server.enableHttp: true 5 | server.httpPort: 7081 6 | server.ioThreads: 16 7 | 8 | # handler.yml 9 | handler.basePath: / 10 | handler.handlers: 11 | - com.networknt.openapi.OpenApiHandler@specification 12 | - com.networknt.openapi.JwtVerifyHandler@jwt 13 | - com.networknt.openapi.SimpleJwtVerifyHandler@sjwt 14 | - com.networknt.openapi.SwtVerifyHandler@swt 15 | - com.networknt.basicauth.BasicAuthHandler@basic 16 | - com.networknt.security.UnifiedSecurityHandler@unified 17 | - com.networknt.apikey.ApiKeyHandler@apikey 18 | 19 | handler.chains.default: 20 | - specification 21 | - unified 22 | 23 | # basic-auth.yml 24 | basic.enabled: true 25 | basic.users: 26 | - username: user1 27 | password: user1pass 28 | paths: 29 | - /v1/address 30 | - /v1/salesforce 31 | - username: user2 32 | password: CRYPT:0754fbc37347c136be7725cbf62b6942:71756e13c2400985d0402ed6f49613d0 33 | paths: 34 | - /v2/pet 35 | - /v2/address 36 | - /v2/party 37 | 38 | # apikey.yml 39 | apikey.pathPrefixAuths: 40 | - pathPrefix: /v1/test1 41 | headerName: x-gateway-apikey 42 | apiKey: abcdefg 43 | - pathPrefix: /v1/test2 44 | headerName: x-apikey 45 | apiKey: CRYPT:3ddd6c8b9bf2afc24d1c94af1dffd518:1bf0cafb19c53e61ddeae626f8906d43 46 | 47 | # client.yml 48 | client.tokenKeyServerUrl: http://localhost:7082 49 | client.tokenKeyUri: /oauth2/N2CMw0HGQXeLvC1wBfln2A/keys 50 | client.tokenKeyClientId: f7d42348-c647-4efb-a52d-4c5787421e72 51 | client.tokenKeyClientSecret: f6h1FTI8Q3-7UScPZDzfXA 52 | client.tokenKeyEnableHttp2: false 53 | client.timeout: 60000 54 | 55 | # security.yml 56 | security.skipPathPrefixes: 57 | - /oauth2 58 | -------------------------------------------------------------------------------- /openapi-security/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | TODO create logger for audit only. 20 | http://stackoverflow.com/questions/2488558/logback-to-log-different-messages-to-two-files 21 | 22 | PROFILER 23 | 24 | NEUTRAL 25 | 26 | 27 | 28 | 30 | 31 | %d{HH:mm:ss.SSS} [%thread] %-5level %class{36}:%L %M - %msg%n 32 | 33 | 34 | 35 | 36 | target/test.log 37 | false 38 | 39 | %d{HH:mm:ss.SSS} [%thread] %-5level %class{36}:%L %M - %msg%n 40 | 41 | 42 | 43 | 44 | 45 | target/audit.log 46 | 47 | %-5level [%thread] %date{ISO8601} %F:%L - %msg%n 48 | true 49 | 50 | 51 | target/audit.log.%i.zip 52 | 1 53 | 5 54 | 55 | 56 | 200MB 57 | 58 | 59 | 60 | 61 | 62 | 63 | 75 | 76 | -------------------------------------------------------------------------------- /openapi-validator/pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 4.0.0 20 | 21 | 22 | com.networknt 23 | light-rest-4j 24 | 2.2.3-SNAPSHOT 25 | ../pom.xml 26 | 27 | 28 | openapi-validator 29 | jar 30 | openapi-validator 31 | An OpenAPI specification 3.0 handler that validates request based on specification. 32 | 33 | 34 | 35 | com.networknt 36 | config 37 | 38 | 39 | com.networknt 40 | dump 41 | 42 | 43 | com.networknt 44 | json-overlay 45 | 46 | 47 | com.networknt 48 | openapi-parser 49 | 50 | 51 | com.networknt 52 | openapi-meta 53 | 54 | 55 | com.networknt 56 | openapi-security 57 | 58 | 59 | com.networknt 60 | body 61 | 62 | 63 | com.networknt 64 | handler 65 | 66 | 67 | com.networknt 68 | validator-config 69 | 70 | 71 | io.undertow 72 | undertow-core 73 | 74 | 75 | com.networknt 76 | json-schema-validator 77 | 78 | 79 | com.fasterxml.jackson.core 80 | jackson-core 81 | 82 | 83 | com.fasterxml.jackson.core 84 | jackson-databind 85 | 86 | 87 | com.fasterxml.jackson.core 88 | jackson-annotations 89 | 90 | 91 | org.slf4j 92 | slf4j-api 93 | 94 | 95 | org.owasp.encoder 96 | encoder 97 | 98 | 99 | 100 | com.networknt 101 | client 102 | test 103 | 104 | 105 | com.networknt 106 | common 107 | test 108 | 109 | 110 | ch.qos.logback 111 | logback-classic 112 | test 113 | 114 | 115 | junit 116 | junit 117 | test 118 | 119 | 120 | org.mockito 121 | mockito-core 122 | test 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /openapi-validator/src/main/java/com/networknt/openapi/SchemaValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Network New Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.networknt.openapi; 18 | 19 | import com.fasterxml.jackson.databind.JsonNode; 20 | import com.fasterxml.jackson.databind.node.ObjectNode; 21 | import com.networknt.jsonoverlay.Overlay; 22 | import com.networknt.oas.model.OpenApi3; 23 | import com.networknt.oas.model.impl.OpenApi3Impl; 24 | import com.networknt.schema.*; 25 | import com.networknt.status.Status; 26 | 27 | import java.util.Set; 28 | 29 | import static java.util.Objects.requireNonNull; 30 | 31 | /** 32 | * Validate a value against the schema defined in an OpenAPI specification. 33 | *

34 | * Supports validation of properties and request/response bodies, and supports schema references. 35 | * 36 | * @author Steve Hu 37 | */ 38 | public class SchemaValidator { 39 | private static final String COMPONENTS_FIELD = "components"; 40 | static final String VALIDATOR_SCHEMA_INVALID_JSON = "ERR11003"; 41 | static final String VALIDATOR_SCHEMA = "ERR11004"; 42 | 43 | private final OpenApi3 api; 44 | private JsonNode jsonNode; 45 | private final SchemaValidatorsConfig defaultConfig; 46 | 47 | /** 48 | * Build a new validator with no API specification. 49 | *

50 | * This will not perform any validation of $ref references that reference local schemas. 51 | * 52 | */ 53 | public SchemaValidator() { 54 | this(null); 55 | } 56 | 57 | /** 58 | * Build a new validator with an API specification. 59 | *

60 | * This will not perform any validation of $ref references that reference local schemas. 61 | * 62 | */ 63 | public SchemaValidator(final OpenApi3 api) { 64 | this(api, false); 65 | } 66 | 67 | /** 68 | * Build a new validator for the given API specification. 69 | * 70 | * @param api The API to build the validator for. If provided, is used to retrieve schemas in components 71 | * for use in references. 72 | */ 73 | public SchemaValidator(final OpenApi3 api, final boolean legacyPathType) { 74 | this.api = api; 75 | this.jsonNode = Overlay.toJson((OpenApi3Impl)api).get("components"); 76 | this.defaultConfig = SchemaValidatorsConfig.builder() 77 | .typeLoose(true) 78 | .pathType(legacyPathType ? PathType.LEGACY : PathType.JSON_POINTER) 79 | .build(); 80 | } 81 | 82 | /** 83 | * Validate the given value against the given property schema. 84 | * 85 | * @param value The value to validate 86 | * @param schema The property schema to validate the value against 87 | * @param config The config model for some validator 88 | * 89 | * @return A status containing error code and description 90 | */ 91 | public Status validate(final JsonNode value, final JsonNode schema, SchemaValidatorsConfig config) { 92 | return doValidate(value, schema, config, null); 93 | } 94 | 95 | /** 96 | * Validate the given value against the given property schema. 97 | * 98 | * @param value The value to validate 99 | * @param schema The property schema to validate the value against 100 | * @param config The config model for some validator 101 | * @param instanceLocation The location being validated 102 | * @return Status object 103 | */ 104 | public Status validate(final JsonNode value, final JsonNode schema, SchemaValidatorsConfig config, JsonNodePath instanceLocation) { 105 | return doValidate(value, schema, config, instanceLocation); 106 | } 107 | 108 | public Status validate(final JsonNode value, final JsonNode schema, String at) { 109 | JsonNodePath instanceLocation = new JsonNodePath(defaultConfig.getPathType()); 110 | if (at != null) { 111 | instanceLocation = instanceLocation.append(at); 112 | } 113 | return validate(value, schema, defaultConfig, instanceLocation); 114 | } 115 | 116 | public Status validate(final JsonNode value, final JsonNode schema, JsonNodePath instanceLocation) { 117 | return doValidate(value, schema, defaultConfig, instanceLocation); 118 | } 119 | 120 | private Status doValidate(final JsonNode value, final JsonNode schema, SchemaValidatorsConfig config, JsonNodePath instanceLocation) { 121 | requireNonNull(schema, "A schema is required"); 122 | if (instanceLocation == null) 123 | instanceLocation = new JsonNodePath(config.getPathType()); 124 | 125 | Status status = null; 126 | Set processingReport = null; 127 | try { 128 | if(jsonNode != null) { 129 | ((ObjectNode)schema).set(COMPONENTS_FIELD, jsonNode); 130 | } 131 | JsonSchema jsonSchema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012).getSchema(schema, config); 132 | processingReport = jsonSchema.validate(jsonSchema.createExecutionContext(), value, value, instanceLocation); 133 | } catch (Exception e) { 134 | e.printStackTrace(); 135 | } 136 | 137 | if(processingReport != null && !processingReport.isEmpty()) { 138 | ValidationMessage vm = processingReport.iterator().next(); 139 | status = new Status(VALIDATOR_SCHEMA, vm.getMessage()); 140 | } 141 | 142 | return status; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /openapi-validator/src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Copyright (c) 2016 Network New Technologies Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | validation.request.path.missing=No API path found that matches request '%s'. 19 | validation.request.operation.notAllowed=%s operation not allowed on path '%s'. 20 | validation.request.body.unexpected=No request body is expected for %s on path '%s'. 21 | validation.request.body.missing=%s on path '%s' requires a request body. None found. 22 | validation.request.parameter.missing=Parameter '%s' is required but is missing. 23 | validation.request.parameter.query.missing=Query parameter '%s' is required on path '%s' but not found in request. 24 | 25 | validation.response.status.unknown=Response status %d not defined for path '%s'. 26 | validation.response.body.missing=%s on path '%s' defines a response schema but no response body found. 27 | validation.schema.invalidJson=Unable to parse JSON - %s 28 | -------------------------------------------------------------------------------- /openapi-validator/src/test/java/com/networknt/openapi/ForwardRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi; 2 | 3 | import com.networknt.common.ContentType; 4 | import com.networknt.config.Config; 5 | import com.networknt.handler.LightHttpHandler; 6 | import com.networknt.httpstring.AttachmentConstants; 7 | 8 | import io.undertow.server.HttpServerExchange; 9 | import io.undertow.util.Headers; 10 | import io.undertow.util.HttpString; 11 | 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | public class ForwardRequestHandler implements LightHttpHandler { 16 | 17 | @Override 18 | public void handleRequest(HttpServerExchange exchange) throws Exception { 19 | String responseBody = null; 20 | if(exchange.getAttachment(AttachmentConstants.REQUEST_BODY) != null) { 21 | responseBody = Config.getInstance().getMapper().writeValueAsString(exchange.getAttachment(AttachmentConstants.REQUEST_BODY)); 22 | } 23 | 24 | List headerNames = exchange.getRequestHeaders().getHeaderNames().stream() 25 | .filter( s -> s.toString().startsWith("todo")) 26 | .collect(Collectors.toList()); 27 | for(HttpString headerName : headerNames) { 28 | String headerValue = exchange.getRequestHeaders().get(headerName).getFirst(); 29 | exchange.getResponseHeaders().put(headerName, headerValue); 30 | } 31 | exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ContentType.APPLICATION_JSON.value()); 32 | exchange.getResponseSender().send(responseBody); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /openapi-validator/src/test/java/com/networknt/openapi/ParameterHandler.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi; 2 | 3 | import static io.undertow.util.PathTemplateMatch.ATTACHMENT_KEY; 4 | 5 | import java.util.Map; 6 | 7 | import com.networknt.handler.Handler; 8 | import com.networknt.handler.MiddlewareHandler; 9 | import com.networknt.utility.ModuleRegistry; 10 | 11 | import io.undertow.Handlers; 12 | import io.undertow.server.HttpHandler; 13 | import io.undertow.server.HttpServerExchange; 14 | import io.undertow.util.PathTemplateMatcher; 15 | 16 | /** 17 | * Simulate com.networknt.handler.Handler.start() 18 | * @author Daniel Zhao 19 | * 20 | */ 21 | public class ParameterHandler implements MiddlewareHandler { 22 | private static PathTemplateMatcher pathTemplateMatcher = new PathTemplateMatcher<>(); 23 | private volatile HttpHandler next; 24 | 25 | static { 26 | pathTemplateMatcher.add("/pets", "0"); 27 | pathTemplateMatcher.add("/pets/{petId}", "1"); 28 | pathTemplateMatcher.add("/pets_simple_array/{petId}", "2"); 29 | pathTemplateMatcher.add("/pets_simple_obj_ep/{petId}", "3"); 30 | pathTemplateMatcher.add("/pets_simple_obj_no_ep/{petId}", "4"); 31 | pathTemplateMatcher.add("/pets_label_array_ep/{petId}", "5"); 32 | pathTemplateMatcher.add("/pets_label_array_no_ep/{petId}", "6"); 33 | pathTemplateMatcher.add("/pets_label_obj_ep/{petId}", "7"); 34 | pathTemplateMatcher.add("/pets_label_obj_no_ep/{petId}", "8"); 35 | pathTemplateMatcher.add("/pets_matrix_array_ep/{petId}", "9"); 36 | pathTemplateMatcher.add("/pets_matrix_array_no_ep/{petId}", "10"); 37 | pathTemplateMatcher.add("/pets_matrix_obj_ep/{petId}", "11"); 38 | pathTemplateMatcher.add("/pets_matrix_obj_no_ep/{petId}", "12"); 39 | pathTemplateMatcher.add("/pets_matrix_pm/{petId}", "13"); 40 | } 41 | 42 | @Override 43 | public void handleRequest(HttpServerExchange exchange) throws Exception { 44 | PathTemplateMatcher.PathMatchResult result = pathTemplateMatcher.match(exchange.getRequestPath()); 45 | 46 | if (result != null) { 47 | exchange.putAttachment(ATTACHMENT_KEY, 48 | new io.undertow.util.PathTemplateMatch(result.getMatchedTemplate(), result.getParameters())); 49 | for (Map.Entry entry : result.getParameters().entrySet()) { 50 | exchange.addQueryParam(entry.getKey(), entry.getValue()); 51 | 52 | exchange.addPathParam(entry.getKey(), entry.getValue()); 53 | } 54 | } 55 | 56 | Handler.next(exchange, next); 57 | } 58 | 59 | @Override 60 | public HttpHandler getNext() { 61 | return next; 62 | } 63 | 64 | @Override 65 | public MiddlewareHandler setNext(HttpHandler next) { 66 | Handlers.handlerNotNull(next); 67 | this.next = next; 68 | return this; 69 | } 70 | 71 | @Override 72 | public boolean isEnabled() { 73 | return true; 74 | } 75 | 76 | @Override 77 | public void register() { 78 | 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /openapi-validator/src/test/java/com/networknt/openapi/ResponseValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.networknt.openapi; 2 | 3 | import com.networknt.body.BodyHandler; 4 | import com.networknt.config.Config; 5 | import com.networknt.handler.LightHttpHandler; 6 | import com.networknt.httpstring.AttachmentConstants; 7 | import com.networknt.status.Status; 8 | import com.networknt.exception.ClientException; 9 | import io.undertow.Handlers; 10 | import io.undertow.Undertow; 11 | import io.undertow.client.ClientRequest; 12 | import io.undertow.client.ClientResponse; 13 | import io.undertow.server.HttpHandler; 14 | import io.undertow.server.HttpServerExchange; 15 | import io.undertow.util.Methods; 16 | import org.junit.AfterClass; 17 | import org.junit.Assert; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.net.URISyntaxException; 24 | import java.util.Map; 25 | import java.util.concurrent.CompletableFuture; 26 | import java.util.concurrent.ExecutionException; 27 | import java.util.concurrent.TimeUnit; 28 | import java.util.concurrent.TimeoutException; 29 | 30 | import static com.networknt.openapi.ValidatorHandlerTest.sendResponse; 31 | 32 | public class ResponseValidatorTest { 33 | static Map responses = Config.getInstance().getJsonMapConfig("responses"); 34 | static Undertow server = null; 35 | static final Logger logger = LoggerFactory.getLogger(ResponseValidatorTest.class); 36 | @Before 37 | public void setUp() { 38 | if(server == null) { 39 | logger.info("starting server"); 40 | OpenApiHandler openApiHandler = new OpenApiHandler(); 41 | BodyHandler bodyHandler = new BodyHandler(); 42 | 43 | TestValidateResponseHandler testValidateResponseHandler = new TestValidateResponseHandler(); 44 | HttpHandler handler = Handlers.routing() 45 | .add(Methods.GET, "/v1/todoItems", testValidateResponseHandler); 46 | ValidatorHandler validatorHandler = new ValidatorHandler(); 47 | validatorHandler.setNext(handler); 48 | handler = validatorHandler; 49 | 50 | bodyHandler.setNext(handler); 51 | handler = bodyHandler; 52 | 53 | openApiHandler.setNext(handler); 54 | handler = openApiHandler; 55 | 56 | server = Undertow.builder() 57 | .addHttpListener(7080, "localhost") 58 | .setHandler(handler) 59 | .build(); 60 | server.start(); 61 | } 62 | } 63 | 64 | @AfterClass 65 | public static void tearDown() throws Exception { 66 | if(server != null) { 67 | try { 68 | Thread.sleep(100); 69 | } catch (InterruptedException ignored) { 70 | 71 | } 72 | server.stop(); 73 | logger.info("The server is stopped."); 74 | } 75 | } 76 | 77 | @Test 78 | public void testValidateResponseContentWithExchange() throws InterruptedException, ClientException, URISyntaxException, TimeoutException, ExecutionException { 79 | ClientRequest clientRequest = new ClientRequest(); 80 | CompletableFuture future = sendResponse(clientRequest, "response1"); 81 | Assert.assertTrue(future.get(3, TimeUnit.SECONDS).getResponseCode() == 200); 82 | } 83 | 84 | @Test 85 | public void testValidateResponseContentWithExchangeError() throws InterruptedException, ClientException, URISyntaxException, TimeoutException, ExecutionException { 86 | ClientRequest clientRequest = new ClientRequest(); 87 | CompletableFuture future = sendResponse(clientRequest, "response2"); 88 | Assert.assertTrue(future.get(3, TimeUnit.SECONDS).getResponseCode() > 300); 89 | } 90 | 91 | public class TestValidateResponseHandler implements LightHttpHandler { 92 | 93 | @Override 94 | public void handleRequest(HttpServerExchange exchange) throws Exception { 95 | 96 | String responseBody = null; 97 | if(exchange.getAttachment(AttachmentConstants.REQUEST_BODY) != null) { 98 | responseBody = Config.getInstance().getMapper().writeValueAsString(exchange.getAttachment(AttachmentConstants.REQUEST_BODY)); 99 | } 100 | final SchemaValidator schemaValidator = new SchemaValidator(OpenApiHandler.helper.openApi3, false); 101 | ResponseValidator validator = new ResponseValidator(schemaValidator); 102 | Status status = validator.validateResponseContent(responseBody, exchange); 103 | if(status == null) { 104 | exchange.getResponseSender().send("good"); 105 | } else { 106 | exchange.setStatusCode(400); 107 | exchange.getResponseSender().send("bad"); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /openapi-validator/src/test/resources/config/client.truststore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networknt/light-rest-4j/27c19bbaf3ad3792007aa89dc6b8f018c52178c0/openapi-validator/src/test/resources/config/client.truststore -------------------------------------------------------------------------------- /openapi-validator/src/test/resources/config/handler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | enabled: true 3 | 4 | # Configuration for the LightHttpHandler. The handler is the base class for all middleware, server and health handlers 5 | # set the Status Object in the AUDIT_INFO, for auditing purposes 6 | # default, if not set:false 7 | auditOnError: true 8 | 9 | # set the StackTrace in the AUDIT_INFO, for auditing purposes 10 | # default, if not set:false 11 | auditStackTrace: true 12 | 13 | handlers: 14 | - com.networknt.handler.sample.SampleHttpHandler1 15 | - com.networknt.handler.sample.SampleHttpHandler2 16 | - com.networknt.handler.sample.SampleHttpHandler3@third 17 | 18 | chains: 19 | secondBeforeFirst: 20 | - com.networknt.handler.sample.SampleHttpHandler2 21 | - com.networknt.handler.sample.SampleHttpHandler1 22 | 23 | paths: 24 | - path: '/test' 25 | method: 'get' 26 | exec: 27 | - secondBeforeFirst 28 | - third 29 | - path: '/v2/health' 30 | method: 'post' 31 | exec: 32 | - secondBeforeFirst 33 | - third 34 | # If there is no matched path, then it goes here first. If this is not set, then an error 35 | # will be returned. 36 | defaultHandlers: 37 | - third 38 | -------------------------------------------------------------------------------- /openapi-validator/src/test/resources/config/openapi-handler-enum.yml: -------------------------------------------------------------------------------- 1 | # openapi-handler.yml 2 | # This configuration file is used to support multiple OpenAPI specifications in the same light-rest-4j instance. 3 | # An indicator to allow multiple openapi specifications. Default to false which only allow one spec named openapi.yml or openapi.yaml or openapi.json. 4 | multipleSpec: ${openapi-handler.multipleSpec:true} 5 | # Path to spec mapping. One or more base paths can map to the same specifications. The key is the base path and the value is the specification name. 6 | # If users want to use multiple specification files in the same instance, each specification must have a unique base path and it must be set as key. 7 | pathSpecMapping: 8 | /v1: openapi-enum 9 | -------------------------------------------------------------------------------- /openapi-validator/src/test/resources/config/openapi-handler-multiple.yml: -------------------------------------------------------------------------------- 1 | # openapi-handler.yml 2 | # This configuration file is used to support multiple OpenAPI specifications in the same light-rest-4j instance. 3 | # An indicator to allow multiple openapi specifications. Default to false which only allow one spec named openapi.yml or openapi.yaml or openapi.json. 4 | multipleSpec: ${openapi-handler.multipleSpec:true} 5 | # Path to spec mapping. One or more base paths can map to the same specifications. The key is the base path and the value is the specification name. 6 | # If users want to use multiple specification files in the same instance, each specification must have a unique base path and it must be set as key. 7 | pathSpecMapping: 8 | /petstore: openapi-petstore 9 | /market: openapi-market 10 | -------------------------------------------------------------------------------- /openapi-validator/src/test/resources/config/openapi-market.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | version: 1.0.0 4 | title: Swagger Market 5 | license: 6 | name: MIT 7 | servers: 8 | - url: 'http://market.swagger.io/market' 9 | paths: 10 | /{store}/products: 11 | get: 12 | summary: Get all products from stores 13 | operationId: listProducts 14 | tags: 15 | - products 16 | parameters: 17 | - name: limit 18 | in: query 19 | description: How many items to return at one time (max 100) 20 | required: false 21 | schema: 22 | type: integer 23 | format: int32 24 | - name: store 25 | in: path 26 | description: The downstream store name 27 | required: true 28 | schema: 29 | type: string 30 | security: 31 | - market_auth: 32 | - 'read:products' 33 | responses: 34 | '200': 35 | description: An paged array of products 36 | content: 37 | application/json: 38 | schema: 39 | type: array 40 | items: 41 | $ref: '#/components/schemas/Product' 42 | example: 43 | - id: 1 44 | name: catten 45 | tag: cat 46 | - id: 2 47 | name: doggy 48 | tag: dog 49 | default: 50 | description: unexpected error 51 | content: 52 | application/json: 53 | schema: 54 | $ref: '#/components/schemas/Error' 55 | post: 56 | summary: Create a product 57 | operationId: createProducts 58 | parameters: 59 | - name: store 60 | in: path 61 | description: The downstream store name 62 | required: true 63 | schema: 64 | type: string 65 | requestBody: 66 | description: Product to add to the target store 67 | required: true 68 | content: 69 | application/json: 70 | schema: 71 | $ref: '#/components/schemas/Product' 72 | tags: 73 | - products 74 | security: 75 | - market_auth: 76 | - 'read:products' 77 | - 'write:products' 78 | responses: 79 | '201': 80 | description: Null response 81 | default: 82 | description: unexpected error 83 | content: 84 | application/json: 85 | schema: 86 | $ref: '#/components/schemas/Error' 87 | components: 88 | securitySchemes: 89 | market_auth: 90 | type: oauth2 91 | description: This API uses OAuth 2 with the client credential grant flow. 92 | flows: 93 | clientCredentials: 94 | tokenUrl: 'https://localhost:6882/token' 95 | scopes: 96 | 'write:products': modify products 97 | 'read:products': read your products 98 | schemas: 99 | Product: 100 | type: object 101 | required: 102 | - id 103 | - name 104 | properties: 105 | id: 106 | type: integer 107 | format: int64 108 | name: 109 | type: string 110 | tag: 111 | type: string 112 | Error: 113 | type: object 114 | required: 115 | - code 116 | - message 117 | properties: 118 | code: 119 | type: integer 120 | format: int32 121 | message: 122 | type: string 123 | -------------------------------------------------------------------------------- /openapi-validator/src/test/resources/config/openapi-validator.yml: -------------------------------------------------------------------------------- 1 | # This is specific OpenAPI validator configuration file. It is introduced to support multiple 2 | # frameworks in the same server instance and it is recommended. If this file cannot be found, 3 | # the generic validator.yml will be loaded as a fallback. 4 | --- 5 | # Enable request validation. Response validation is not done on the server but client. 6 | enabled: true 7 | # Log error message if validation error occurs 8 | logError: true 9 | # Skip body validation set to true if used in light-router, light-proxy and light-spring-boot. 10 | skipBodyValidation: false 11 | validateResponse: true 12 | 13 | # When a field is set as nullable in the OpenAPI specification, the schema validator validates that it is nullable 14 | # however continues with validation against the nullable field 15 | 16 | # If handleNullableField is set to true && incoming field is nullable && value is field: null --> succeed 17 | # If handleNullableField is set to false && incoming field is nullable && value is field: null --> it is up to the type 18 | # validator using the SchemaValidator to handle it. 19 | handleNullableField: true 20 | -------------------------------------------------------------------------------- /openapi-validator/src/test/resources/config/responses.yml: -------------------------------------------------------------------------------- 1 | response1: 2 | - id: 1a7f12c2-f591-4344-9c93-56d16cfdb0ce 3 | content: response1 4 | trace: 1 5 | - id: 1b7f12c2-f591-4344-9c93-56d16cfdb0ce 6 | content: response1 7 | trace: 2 8 | response2: 9 | - id: 2a7f12c2-f591-4344-9c93-56d16cfdb0ce 10 | trace: 3 11 | -------------------------------------------------------------------------------- /openapi-validator/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | TODO create logger for audit only. 20 | http://stackoverflow.com/questions/2488558/logback-to-log-different-messages-to-two-files 21 | 22 | PROFILER 23 | 24 | NEUTRAL 25 | 26 | 27 | 28 | 30 | 31 | %d{HH:mm:ss.SSS} [%thread] %-5marker %-5level %logger{36} - %msg%n 32 | 33 | 34 | 35 | 36 | target/test.log 37 | false 38 | 39 | %d{HH:mm:ss.SSS} [%thread] %-5level %class{36}:%L %M - %msg%n 40 | 41 | 42 | 43 | 44 | 45 | target/audit.log 46 | 47 | %-5level [%thread] %date{ISO8601} %F:%L - %msg%n 48 | true 49 | 50 | 51 | target/audit.log.%i.zip 52 | 1 53 | 5 54 | 55 | 56 | 200MB 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /specification/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | light-rest-4j 7 | com.networknt 8 | 2.2.3-SNAPSHOT 9 | 10 | 4.0.0 11 | jar 12 | specification 13 | specification 14 | 15 | 16 | 17 | com.networknt 18 | config 19 | 20 | 21 | com.networknt 22 | utility 23 | 24 | 25 | com.networknt 26 | security 27 | 28 | 29 | com.networknt 30 | handler 31 | 32 | 33 | com.networknt 34 | status 35 | 36 | 37 | io.undertow 38 | undertow-core 39 | 40 | 41 | com.fasterxml.jackson.core 42 | jackson-databind 43 | 44 | 45 | org.slf4j 46 | slf4j-api 47 | 48 | 49 | 50 | com.networknt 51 | client 52 | test 53 | 54 | 55 | ch.qos.logback 56 | logback-classic 57 | test 58 | 59 | 60 | junit 61 | junit 62 | test 63 | 64 | 65 | org.apache.commons 66 | commons-text 67 | test 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /specification/src/main/java/com/networknt/specification/FaviconHandler.java: -------------------------------------------------------------------------------- 1 | package com.networknt.specification; 2 | 3 | import com.networknt.config.Config; 4 | import com.networknt.handler.LightHttpHandler; 5 | import io.undertow.server.HttpServerExchange; 6 | import io.undertow.util.HttpString; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.InputStream; 11 | import java.io.OutputStream; 12 | 13 | /** 14 | * This is the handler that returns the favicon.ico from the resources config folder for 15 | * swagger ui rendering. 16 | * 17 | * @author Steve Hu 18 | */ 19 | public class FaviconHandler implements LightHttpHandler { 20 | private static final Logger logger = LoggerFactory.getLogger(FaviconHandler.class); 21 | public FaviconHandler(){ 22 | if(logger.isInfoEnabled()) logger.info("FaviconHandler is initialized."); 23 | } 24 | 25 | @Override 26 | public void handleRequest(HttpServerExchange exchange) throws Exception { 27 | if (exchange.isInIoThread()) { 28 | exchange.dispatch(this); 29 | return; 30 | } 31 | exchange.startBlocking(); 32 | exchange.getResponseHeaders().add(new HttpString("Content-Type"), "image/x-icon"); 33 | try (InputStream inputStream = Config.getInstance().getInputStreamFromFile("favicon.ico"); OutputStream outputStream = exchange.getOutputStream()) { 34 | byte[] buf = new byte[8192]; 35 | int c; 36 | while ((c = inputStream.read(buf, 0, buf.length)) > 0) { 37 | outputStream.write(buf, 0, c); 38 | outputStream.flush(); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /specification/src/main/java/com/networknt/specification/SpecDisplayHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Network New Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.networknt.specification; 18 | 19 | import com.networknt.config.Config; 20 | import com.networknt.handler.LightHttpHandler; 21 | import com.networknt.utility.ModuleRegistry; 22 | import io.undertow.server.HttpServerExchange; 23 | import io.undertow.util.HttpString; 24 | 25 | /** 26 | * Display API Specification 27 | * 28 | * @author Gavin Chen 29 | */ 30 | public class SpecDisplayHandler implements LightHttpHandler { 31 | static SpecificationConfig config = (SpecificationConfig)Config.getInstance().getJsonObjectConfig(SpecificationConfig.CONFIG_NAME, SpecificationConfig.class); 32 | public SpecDisplayHandler(){ 33 | if(logger.isInfoEnabled()) logger.info("SpecDisplayHandler is constructed"); 34 | ModuleRegistry.registerModule(SpecificationConfig.CONFIG_NAME, SpecDisplayHandler.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(SpecificationConfig.CONFIG_NAME), null); 35 | } 36 | 37 | @Override 38 | public void handleRequest(HttpServerExchange exchange) throws Exception { 39 | SpecificationConfig config = (SpecificationConfig)Config.getInstance().getJsonObjectConfig(SpecificationConfig.CONFIG_NAME, SpecificationConfig.class); 40 | final String payload = Config.getInstance().getStringFromFile(config.getFileName()); 41 | exchange.getResponseHeaders().add(new HttpString("Content-Type"), config.getContentType()); 42 | exchange.getResponseSender().send(payload); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /specification/src/main/java/com/networknt/specification/SpecSwaggerUIHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Network New Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.networknt.specification; 18 | import com.networknt.config.Config; 19 | import com.networknt.handler.LightHttpHandler; 20 | import io.undertow.server.HttpServerExchange; 21 | import io.undertow.util.HttpString; 22 | 23 | /** 24 | * Display API Specification in Swagger editor UI 25 | * 26 | * @author Gavin Chen 27 | */ 28 | public class SpecSwaggerUIHandler implements LightHttpHandler { 29 | // The url in this html is using the spec.yaml API which is served by SpecDisplayHandler. It needs to be in sync with the handler.yml 30 | public static String swaggerUITemplate = 31 | "\n" + 32 | "\n" + 33 | " OpenAPI Spec\n" + 34 | " \n" + 35 | "\n" + 36 | "\n" + 37 | "

\n" + 38 | "\n" + 39 | "\n" + 40 | "\n" + 61 | "\n" + 62 | ""; 63 | public SpecSwaggerUIHandler(){} 64 | 65 | 66 | 67 | @Override 68 | public void handleRequest(HttpServerExchange exchange) throws Exception { 69 | SpecificationConfig config = (SpecificationConfig) Config.getInstance().getJsonObjectConfig(SpecificationConfig.CONFIG_NAME, SpecificationConfig.class); 70 | exchange.getResponseHeaders().add(new HttpString("Content-Type"), "text/html"); 71 | exchange.getResponseSender().send(getDisplayHtml()); 72 | } 73 | 74 | private String getDisplayHtml() { 75 | return swaggerUITemplate; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /specification/src/main/java/com/networknt/specification/SpecificationConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Network New Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.networknt.specification; 18 | 19 | import com.networknt.config.schema.ConfigSchema; 20 | import com.networknt.config.schema.OutputFormat; 21 | import com.networknt.config.schema.StringField; 22 | 23 | /** 24 | * Config class for Spec display Handler 25 | * 26 | */ 27 | @ConfigSchema( 28 | configName = "specification", 29 | configKey = "specification", 30 | configDescription = "Specification type and file name definition", 31 | outputFormats = {OutputFormat.JSON_SCHEMA, OutputFormat.YAML} 32 | ) 33 | public class SpecificationConfig { 34 | public static final String CONFIG_NAME = "specification"; 35 | public static final String FILE_NAME = "fileName"; 36 | public static final String CONTENT_TYPE = "contentType"; 37 | 38 | @StringField( 39 | configFieldName = FILE_NAME, 40 | externalizedKeyName = FILE_NAME, 41 | externalized = true, 42 | defaultValue = "openapi.yaml", 43 | description = "The filename with path of the specification file, and usually it is openapi.yaml" 44 | ) 45 | String fileName; 46 | 47 | @StringField( 48 | configFieldName = CONTENT_TYPE, 49 | externalizedKeyName = CONTENT_TYPE, 50 | externalized = true, 51 | defaultValue = "text/yaml", 52 | description = "The content type of the specification file. In most cases, we are using yaml format." 53 | ) 54 | String contentType; 55 | 56 | public SpecificationConfig() { 57 | } 58 | 59 | public String getFileName() { 60 | return fileName; 61 | } 62 | 63 | public void setFileName(String fileName) { 64 | this.fileName = fileName; 65 | } 66 | 67 | public String getContentType() { 68 | return contentType; 69 | } 70 | 71 | public void setContentType(String contentType) { 72 | this.contentType = contentType; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /specification/src/main/resources/config/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networknt/light-rest-4j/27c19bbaf3ad3792007aa89dc6b8f018c52178c0/specification/src/main/resources/config/favicon.ico -------------------------------------------------------------------------------- /specification/src/main/resources/config/specification-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema" : "http://json-schema.org/draft-07/schema#", 3 | "type" : "object", 4 | "required" : [ "fileName", "contentType" ], 5 | "properties" : { 6 | "fileName" : { 7 | "type" : "string", 8 | "description" : "The filename with path of the specification file, and usually it is openapi.yaml", 9 | "default" : "openapi.yaml" 10 | }, 11 | "contentType" : { 12 | "type" : "string", 13 | "description" : "The content type of the specification file. In most cases, we are using yaml format.", 14 | "default" : "text/yaml" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /specification/src/main/resources/config/specification.yaml: -------------------------------------------------------------------------------- 1 | # Specification type and file name definition 2 | # The filename with path of the specification file, and usually it is openapi.yaml 3 | fileName: ${specification.fileName:openapi.yaml} 4 | # The content type of the specification file. In most cases, we are using yaml format. 5 | contentType: ${specification.contentType:text/yaml} 6 | -------------------------------------------------------------------------------- /specification/src/main/resources/config/specification.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Specification type and file name definition 3 | # The filename with path of the specification file, and usually it is openapi.yaml 4 | fileName: ${specification.fileName:openapi.yaml} 5 | # The content type of the specification file. In most cases, we are using yaml format. 6 | contentType: ${specification.contentType:text/yaml} 7 | -------------------------------------------------------------------------------- /specification/src/test/java/com/networknt/specification/SpecDisplayHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Network New Technologies Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.networknt.specification; 18 | 19 | import com.networknt.client.Http2Client; 20 | import com.networknt.exception.ClientException; 21 | import io.undertow.Handlers; 22 | import io.undertow.Undertow; 23 | import io.undertow.client.ClientConnection; 24 | import io.undertow.client.ClientRequest; 25 | import io.undertow.client.ClientResponse; 26 | import io.undertow.server.HttpHandler; 27 | import io.undertow.server.RoutingHandler; 28 | import io.undertow.util.Headers; 29 | import io.undertow.util.Methods; 30 | import org.junit.AfterClass; 31 | import org.junit.Assert; 32 | import org.junit.BeforeClass; 33 | import org.junit.Test; 34 | import org.slf4j.Logger; 35 | import org.slf4j.LoggerFactory; 36 | import org.xnio.IoUtils; 37 | import org.xnio.OptionMap; 38 | 39 | import java.net.URI; 40 | import java.util.concurrent.CountDownLatch; 41 | import java.util.concurrent.atomic.AtomicReference; 42 | 43 | /** 44 | * Created by Gavin Chen. 45 | */ 46 | public class SpecDisplayHandlerTest { 47 | static final Logger logger = LoggerFactory.getLogger(SpecDisplayHandlerTest.class); 48 | 49 | static Undertow server = null; 50 | 51 | @BeforeClass 52 | public static void setUp() { 53 | if(server == null) { 54 | logger.info("starting server"); 55 | HttpHandler handler = getTestHandler(); 56 | server = Undertow.builder() 57 | .addHttpListener(7080, "localhost") 58 | .setHandler(handler) 59 | .build(); 60 | server.start(); 61 | } 62 | } 63 | 64 | @AfterClass 65 | public static void tearDown() throws Exception { 66 | if(server != null) { 67 | try { 68 | Thread.sleep(100); 69 | } catch (InterruptedException ignored) { 70 | 71 | } 72 | server.stop(); 73 | logger.info("The server is stopped."); 74 | } 75 | } 76 | 77 | static RoutingHandler getTestHandler() { 78 | return Handlers.routing().add(Methods.GET, "/spec.yml", new SpecDisplayHandler()); 79 | } 80 | 81 | @Test 82 | public void testSpec() throws Exception { 83 | final Http2Client client = Http2Client.getInstance(); 84 | final CountDownLatch latch = new CountDownLatch(1); 85 | final ClientConnection connection; 86 | try { 87 | connection = client.connect(new URI("http://localhost:7080"), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.EMPTY).get(); 88 | } catch (Exception e) { 89 | throw new ClientException(e); 90 | } 91 | final AtomicReference reference = new AtomicReference<>(); 92 | try { 93 | ClientRequest request = new ClientRequest().setPath("/spec.yml").setMethod(Methods.GET); 94 | request.getRequestHeaders().put(Headers.HOST, "localhost"); 95 | connection.sendRequest(request, client.createClientCallback(reference, latch)); 96 | latch.await(); 97 | } catch (Exception e) { 98 | logger.error("Exception: ", e); 99 | throw new ClientException(e); 100 | } finally { 101 | IoUtils.safeClose(connection); 102 | } 103 | int statusCode = reference.get().getResponseCode(); 104 | String body = reference.get().getAttachment(Http2Client.RESPONSE_BODY); 105 | Assert.assertEquals(200, statusCode); 106 | Assert.assertNotNull( body); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /specification/src/test/java/com/networknt/specification/SpecSwaggerUIHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.networknt.specification; 2 | 3 | import com.networknt.client.Http2Client; 4 | import com.networknt.exception.ClientException; 5 | import io.undertow.Handlers; 6 | import io.undertow.Undertow; 7 | import io.undertow.client.ClientConnection; 8 | import io.undertow.client.ClientRequest; 9 | import io.undertow.client.ClientResponse; 10 | import io.undertow.server.HttpHandler; 11 | import io.undertow.server.RoutingHandler; 12 | import io.undertow.util.Headers; 13 | import io.undertow.util.Methods; 14 | import org.junit.AfterClass; 15 | import org.junit.Assert; 16 | import org.junit.BeforeClass; 17 | import org.junit.Test; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import org.xnio.IoUtils; 21 | import org.xnio.OptionMap; 22 | 23 | import java.net.URI; 24 | import java.util.concurrent.CountDownLatch; 25 | import java.util.concurrent.atomic.AtomicReference; 26 | 27 | public class SpecSwaggerUIHandlerTest { 28 | static final Logger logger = LoggerFactory.getLogger(SpecSwaggerUIHandlerTest.class); 29 | 30 | static Undertow server = null; 31 | 32 | @BeforeClass 33 | public static void setUp() { 34 | if(server == null) { 35 | logger.info("starting server"); 36 | HttpHandler handler = getTestHandler(); 37 | server = Undertow.builder() 38 | .addHttpListener(7080, "localhost") 39 | .setHandler(handler) 40 | .build(); 41 | server.start(); 42 | } 43 | } 44 | 45 | @AfterClass 46 | public static void tearDown() throws Exception { 47 | if(server != null) { 48 | try { 49 | Thread.sleep(100); 50 | } catch (InterruptedException ignored) { 51 | 52 | } 53 | server.stop(); 54 | logger.info("The server is stopped."); 55 | } 56 | } 57 | 58 | static RoutingHandler getTestHandler() { 59 | return Handlers.routing().add(Methods.GET, "/specui.html", new SpecSwaggerUIHandler()); 60 | } 61 | 62 | @Test 63 | public void testSpecUI() throws Exception { 64 | final Http2Client client = Http2Client.getInstance(); 65 | final CountDownLatch latch = new CountDownLatch(1); 66 | final ClientConnection connection; 67 | try { 68 | connection = client.connect(new URI("http://localhost:7080"), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.EMPTY).get(); 69 | } catch (Exception e) { 70 | throw new ClientException(e); 71 | } 72 | final AtomicReference reference = new AtomicReference<>(); 73 | try { 74 | ClientRequest request = new ClientRequest().setPath("/specui.html").setMethod(Methods.GET); 75 | request.getRequestHeaders().put(Headers.HOST, "localhost"); 76 | connection.sendRequest(request, client.createClientCallback(reference, latch)); 77 | latch.await(); 78 | } catch (Exception e) { 79 | logger.error("Exception: ", e); 80 | throw new ClientException(e); 81 | } finally { 82 | IoUtils.safeClose(connection); 83 | } 84 | int statusCode = reference.get().getResponseCode(); 85 | String body = reference.get().getAttachment(Http2Client.RESPONSE_BODY); 86 | Assert.assertEquals(200, statusCode); 87 | Assert.assertNotNull( body); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /specification/src/test/resources/config/client.truststore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networknt/light-rest-4j/27c19bbaf3ad3792007aa89dc6b8f018c52178c0/specification/src/test/resources/config/client.truststore -------------------------------------------------------------------------------- /specification/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | TODO create logger for audit only. 20 | http://stackoverflow.com/questions/2488558/logback-to-log-different-messages-to-two-files 21 | 22 | PROFILER 23 | 24 | NEUTRAL 25 | 26 | 27 | 28 | 30 | 31 | %d{HH:mm:ss.SSS} [%thread] %-5marker %-5level %logger{36} - %msg%n 32 | 33 | 34 | 35 | 36 | target/test.log 37 | false 38 | 39 | %d{HH:mm:ss.SSS} [%thread] %-5level %class{36}:%L %M - %msg%n 40 | 41 | 42 | 43 | 44 | 45 | target/audit.log 46 | 47 | %-5level [%thread] %date{ISO8601} %F:%L - %msg%n 48 | true 49 | 50 | 51 | target/audit.log.%i.zip 52 | 1 53 | 5 54 | 55 | 56 | 200MB 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /validator-config/pom.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 4.0.0 20 | 21 | 22 | com.networknt 23 | light-rest-4j 24 | 2.2.3-SNAPSHOT 25 | ../pom.xml 26 | 27 | 28 | validator-config 29 | jar 30 | validator-config 31 | A config module for openapi-validator and light-aws-lambda. 32 | 33 | 34 | 35 | com.networknt 36 | config 37 | 38 | 39 | org.slf4j 40 | slf4j-api 41 | 42 | 43 | 44 | ch.qos.logback 45 | logback-classic 46 | test 47 | 48 | 49 | junit 50 | junit 51 | test 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /validator-config/src/main/resources/config/openapi-validator-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema" : "http://json-schema.org/draft-07/schema#", 3 | "type" : "object", 4 | "required" : [ "enabled", "logError", "legacyPathType", "skipBodyValidation", "validateResponse", "handleNullableField", "skipPathPrefixes" ], 5 | "properties" : { 6 | "enabled" : { 7 | "type" : "boolean", 8 | "description" : "Enable request validation. Response validation is not done on the server but client.", 9 | "default" : true 10 | }, 11 | "logError" : { 12 | "type" : "boolean", 13 | "description" : "Log error message if validation error occurs", 14 | "default" : true 15 | }, 16 | "legacyPathType" : { 17 | "type" : "boolean", 18 | "description" : "By default, the json-schema-validator will return the error message using JSON_POINTER path type. If you want\nto make sure that the error message is the same as the older version, you can set the legacyPathType to true." 19 | }, 20 | "skipBodyValidation" : { 21 | "type" : "boolean", 22 | "description" : "Skip body validation set to true if used in light-router, light-proxy and light-spring-boot." 23 | }, 24 | "validateResponse" : { 25 | "type" : "boolean", 26 | "description" : "Enable response validation." 27 | }, 28 | "handleNullableField" : { 29 | "type" : "boolean", 30 | "description" : "When a field is set as nullable in the OpenAPI specification, the schema validator validates that it is nullable\nhowever continues with validation against the nullable field\n\nIf handleNullableField is set to true && incoming field is nullable && value is field: null --> succeed\nIf handleNullableField is set to false && incoming field is nullable && value is field: null --> it is up to the type\nvalidator using the SchemaValidator to handle it.", 31 | "default" : true 32 | }, 33 | "skipPathPrefixes" : { 34 | "type" : "array", 35 | "description" : "Define a list of path prefixes to skip the validation to ease the configuration for the\nhandler.yml so that users can define some endpoints without validation even through it uses\nthe default chain. This is particularly useful in the light-gateway use case as the same\ninstance might be shared with multiple consumers and providers with different validation\nrequirement. The format is a list of strings separated with commas or a JSON list in\nvalues.yml definition from config server, or you can use yaml format in this file.", 36 | "items" : { 37 | "type" : "string" 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /validator-config/src/main/resources/config/openapi-validator.yaml: -------------------------------------------------------------------------------- 1 | # Default openapi-validator configuration 2 | # Enable request validation. Response validation is not done on the server but client. 3 | enabled: ${openapi-validator.enabled:true} 4 | # Log error message if validation error occurs 5 | logError: ${openapi-validator.logError:true} 6 | # By default, the json-schema-validator will return the error message using JSON_POINTER path type. If you want 7 | # to make sure that the error message is the same as the older version, you can set the legacyPathType to true. 8 | legacyPathType: ${openapi-validator.legacyPathType:false} 9 | # Skip body validation set to true if used in light-router, light-proxy and light-spring-boot. 10 | skipBodyValidation: ${openapi-validator.skipBodyValidation:false} 11 | # Enable response validation. 12 | validateResponse: ${openapi-validator.validateResponse:false} 13 | # When a field is set as nullable in the OpenAPI specification, the schema validator validates that it is nullable 14 | # however continues with validation against the nullable field 15 | # 16 | # If handleNullableField is set to true && incoming field is nullable && value is field: null --> succeed 17 | # If handleNullableField is set to false && incoming field is nullable && value is field: null --> it is up to the type 18 | # validator using the SchemaValidator to handle it. 19 | handleNullableField: ${openapi-validator.handleNullableField:true} 20 | # Define a list of path prefixes to skip the validation to ease the configuration for the 21 | # handler.yml so that users can define some endpoints without validation even through it uses 22 | # the default chain. This is particularly useful in the light-gateway use case as the same 23 | # instance might be shared with multiple consumers and providers with different validation 24 | # requirement. The format is a list of strings separated with commas or a JSON list in 25 | # values.yml definition from config server, or you can use yaml format in this file. 26 | skipPathPrefixes: ${openapi-validator.skipPathPrefixes:} 27 | -------------------------------------------------------------------------------- /validator-config/src/main/resources/config/openapi-validator.yml: -------------------------------------------------------------------------------- 1 | # Default openapi-validator configuration 2 | --- 3 | # Enable request validation. Response validation is not done on the server but client. 4 | enabled: ${openapi-validator.enabled:true} 5 | # Log error message if validation error occurs 6 | logError: ${openapi-validator.logError:true} 7 | # By default, the json-schema-validator will return the error message using JSON_POINTER path type. If you want 8 | # to make sure that the error message is the same as the older version, you can set the legacyPathType to true. 9 | legacyPathType: ${openapi-validator.legacyPathType:false} 10 | # Skip body validation set to true if used in light-router, light-proxy and light-spring-boot. 11 | skipBodyValidation: ${openapi-validator.skipBodyValidation:false} 12 | # Enable response validation. 13 | validateResponse: ${openapi-validator.validateResponse:false} 14 | # When a field is set as nullable in the OpenAPI specification, the schema validator validates that it is nullable 15 | # however continues with validation against the nullable field 16 | 17 | # If handleNullableField is set to true && incoming field is nullable && value is field: null --> succeed 18 | # If handleNullableField is set to false && incoming field is nullable && value is field: null --> it is up to the type 19 | # validator using the SchemaValidator to handle it. 20 | handleNullableField: ${openapi-validator.handleNullableField:true} 21 | 22 | # Define a list of path prefixes to skip the validation to ease the configuration for the 23 | # handler.yml so that users can define some endpoints without validation even through it uses 24 | # the default chain. This is particularly useful in the light-gateway use case as the same 25 | # instance might be shared with multiple consumers and providers with different validation 26 | # requirement. The format is a list of strings separated with commas or a JSON list in 27 | # values.yml definition from config server, or you can use yaml format in this file. 28 | skipPathPrefixes: ${openapi-validator.skipPathPrefixes:} 29 | --------------------------------------------------------------------------------