From 9d9b4a949cc58ecc74a7ead4594e3de8a9c71e38 Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 28 Sep 2021 09:03:29 +0200 Subject: [PATCH] SEBSERV-140 springdoc-openapi integration --- pom.xml | 9 +++ .../webservice/WebserviceConfig.java | 18 +++++ .../weblayer/api/EntityController.java | 74 +++++++++++++++++-- .../config/application-dev.properties | 2 +- .../config/application-ws.properties | 9 ++- .../resources/config/application.properties | 2 +- 6 files changed, 104 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 819389e3..966b7d27 100644 --- a/pom.xml +++ b/pom.xml @@ -287,6 +287,15 @@ org.springframework.boot spring-boot-starter-validation + + + + org.springdoc + springdoc-openapi-ui + 1.5.10 + + + diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java index c1f79837..1c245cab 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/WebserviceConfig.java @@ -16,9 +16,27 @@ import org.springframework.context.annotation.Lazy; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.OAuthFlow; +import io.swagger.v3.oas.annotations.security.OAuthFlows; +import io.swagger.v3.oas.annotations.security.SecurityScheme; @Configuration @WebServiceProfile +@OpenAPIDefinition(info = @Info(title = "SEB Server", version = "v1")) +@SecurityScheme( + name = "oauth2", + type = SecuritySchemeType.OAUTH2, + bearerFormat = "JWT", + flows = @OAuthFlows( + password = @OAuthFlow( + tokenUrl = "/oauth/token" + + ) + + )) public class WebserviceConfig { @Lazy diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java index 39a8e0d6..d539426d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java @@ -25,7 +25,6 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -54,12 +53,18 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; /** Abstract Entity-Controller that defines generic Entity rest API endpoints that are supported * by all entity types. * * @param The concrete Entity domain-model type used on all GET, PUT * @param The concrete Entity domain-model type used for POST methods (new) */ +@SecurityRequirement(name = "oauth2") public abstract class EntityController { protected final AuthorizationService authorization; @@ -129,6 +134,39 @@ public abstract class EntityController { * descending sort order. * @param allRequestParams a MultiValueMap of all request parameter that is used for filtering. * @return Page of domain-model-entities of specified type */ + @Operation( + summary = "Get a page of the specific domain entity. Sorting and filtering is applied before paging", + requestBody = @RequestBody( + content = { @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) }), + parameters = { + @Parameter( + name = Page.ATTR_PAGE_NUMBER, + description = "The number of the page to get from the whole list. If the page does not exists, the API retruns with the first page."), + @Parameter( + name = Page.ATTR_PAGE_SIZE, + description = "The size of the page to get."), + @Parameter( + name = Page.ATTR_SORT, + description = "the sort parameter to sort the list of entities before paging\n" + + "the sort parameter is the name of the entity-model attribute to sort with a leading '-' sign for\n" + + "descending sort order. Note that not all entity-model attribute are suited for sorting while the most are.", + example = "-name"), + @Parameter( + name = API.PARAM_INSTITUTION_ID, + description = "The institution identifier of the request.\n" + + "Default is the institution identifier of the institution of the current user"), + @Parameter( + name = "filterCriteria", + description = "Additional filter criterias \n" + + "The filter criteria attributes accepted by this API depend on the actual entity model (domain entity)\n" + + "and are query of the form [domain-attribute-name]=[filter-value]. E.g.: name=abc or type=EXAM.\n" + + "For OpenAPI 3 input please use the form: {\"columnName\":\"filterValue\"}\n" + + "Usually filter attributes of text type are treated as SQL wildcard with %[text]% to filter all text containing\n" + + "a given text-snippet.", + example = "{\"name\":\"ethz\"}", + required = false, + allowEmptyValue = true) + }) @RequestMapping( method = RequestMethod.GET, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, @@ -141,13 +179,13 @@ public abstract class EntityController { @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber, @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize, @RequestParam(name = Page.ATTR_SORT, required = false) final String sort, - @RequestParam final MultiValueMap allRequestParams, + @RequestParam final MultiValueMap filterCriteria, final HttpServletRequest request) { // at least current user must have read access for specified entity type within its own institution checkReadPrivilege(institutionId); - final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString()); + final FilterMap filterMap = new FilterMap(filterCriteria, request.getQueryString()); populateFilterMap(filterMap, institutionId, sort); return this.paginationService.getPage( @@ -176,6 +214,28 @@ public abstract class EntityController { // * GET (names) // ****************** + @Operation( + summary = "Get a filtered list of specific entity name keys.\n" + + "An entity name key is a minimal entity data object with the entity-type, modelId and the name of the entity.", + requestBody = @RequestBody( + content = { @Content(mediaType = MediaType.APPLICATION_FORM_URLENCODED_VALUE) }), + parameters = { + @Parameter( + name = API.PARAM_INSTITUTION_ID, + description = "The institution identifier of the request.\n" + + "Default is the institution identifier of the institution of the current user"), + @Parameter( + name = "filterCriteria", + description = "Additional filter criterias \n" + + "The filter criteria attributes accepted by this API depend on the actual entity model (domain entity)\n" + + "and are query of the form [domain-attribute-name]=[filter-value]. E.g.: name=abc or type=EXAM.\n" + + "For OpenAPI 3 input please use the form: {\"columnName\":\"filterValue\"}\n" + + "Usually filter attributes of text type are treated as SQL wildcard with %[text]% to filter all text containing\n" + + "a given text-snippet.", + example = "{\"name\":\"ethz\"}", + required = false, + allowEmptyValue = true) + }) @RequestMapping( path = API.NAMES_PATH_SEGMENT, method = RequestMethod.GET, @@ -186,13 +246,13 @@ public abstract class EntityController { name = API.PARAM_INSTITUTION_ID, required = true, defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, - @RequestParam final MultiValueMap allRequestParams, + @RequestParam final MultiValueMap filterCriteria, final HttpServletRequest request) { // at least current user must have read access for specified entity type within its own institution checkReadPrivilege(institutionId); - final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString()); + final FilterMap filterMap = new FilterMap(filterCriteria, request.getQueryString()); // if current user has no read access for specified entity type within other institution then its own institution, // then the current users institutionId is put as a SQL filter criteria attribute to extends query performance @@ -285,7 +345,7 @@ public abstract class EntityController { consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public T create( - @RequestParam final MultiValueMap allRequestParams, + @RequestParam final MultiValueMap formParams, @RequestParam( name = API.PARAM_INSTITUTION_ID, required = true, @@ -295,7 +355,7 @@ public abstract class EntityController { // check write privilege for requested institution and concrete entityType this.checkWritePrivilege(institutionId); - final POSTMapper postMap = new POSTMapper(allRequestParams, request.getQueryString()) + final POSTMapper postMap = new POSTMapper(formParams, request.getQueryString()) .putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId)); final M requestModel = this.createNew(postMap); diff --git a/src/main/resources/config/application-dev.properties b/src/main/resources/config/application-dev.properties index b11db757..8194d5a1 100644 --- a/src/main/resources/config/application-dev.properties +++ b/src/main/resources/config/application-dev.properties @@ -1,4 +1,4 @@ -spring.profiles.include=dev-ws,dev-gui +spring.profiles.include=dev-ws sebserver.test.property=This is the development Setup diff --git a/src/main/resources/config/application-ws.properties b/src/main/resources/config/application-ws.properties index 9e6c6799..9c300053 100644 --- a/src/main/resources/config/application-ws.properties +++ b/src/main/resources/config/application-ws.properties @@ -40,7 +40,14 @@ sebserver.webservice.distributed=false sebserver.webservice.http.external.scheme=https sebserver.webservice.http.external.servername= sebserver.webservice.http.external.port= -sebserver.webservice.http.redirect.gui=/gui +sebserver.webservice.http.redirect.gui=/ + +### Open API Documentation +springdoc.swagger-ui.oauth.clientId=guiClient +springdoc.swagger-ui.oauth.clientSecret=${sebserver.password} +#springdoc.consumes-to-match=application/json,application/x-www-form-urlencoded +#springdoc.default-consumes-media-type=application/x-www-form-urlencoded +springdoc.paths-to-exclude=/exam-api,/exam-api/discovery,/sebserver/error,/sebserver/check,/oauth,/exam-api/v1/* ### webservice API sebserver.webservice.api.admin.clientId=guiClient diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index 2f6bd64a..873928af 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -1,5 +1,5 @@ spring.application.name=SEB Server -spring.profiles.active=ws,gui,dev +spring.profiles.active=ws,dev sebserver.version=@sebserver-version@ ##########################################################