SEBSERV-140 springdoc-openapi integration

This commit is contained in:
anhefti 2021-09-28 09:03:29 +02:00
parent dae6ff1df6
commit 9d9b4a949c
6 changed files with 104 additions and 10 deletions

View file

@ -288,6 +288,15 @@
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
</dependency> </dependency>
<!-- Springdoc-openapi -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.10</version>
</dependency>
<!-- Flyway --> <!-- Flyway -->
<dependency> <dependency>
<groupId>org.flywaydb</groupId> <groupId>org.flywaydb</groupId>

View file

@ -16,9 +16,27 @@ import org.springframework.context.annotation.Lazy;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; 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 @Configuration
@WebServiceProfile @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 { public class WebserviceConfig {
@Lazy @Lazy

View file

@ -25,7 +25,6 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; 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.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; 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 /** Abstract Entity-Controller that defines generic Entity rest API endpoints that are supported
* by all entity types. * by all entity types.
* *
* @param <T> The concrete Entity domain-model type used on all GET, PUT * @param <T> The concrete Entity domain-model type used on all GET, PUT
* @param <M> The concrete Entity domain-model type used for POST methods (new) */ * @param <M> The concrete Entity domain-model type used for POST methods (new) */
@SecurityRequirement(name = "oauth2")
public abstract class EntityController<T extends Entity, M extends Entity> { public abstract class EntityController<T extends Entity, M extends Entity> {
protected final AuthorizationService authorization; protected final AuthorizationService authorization;
@ -129,6 +134,39 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
* descending sort order. * descending sort order.
* @param allRequestParams a MultiValueMap of all request parameter that is used for filtering. * @param allRequestParams a MultiValueMap of all request parameter that is used for filtering.
* @return Page of domain-model-entities of specified type */ * @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( @RequestMapping(
method = RequestMethod.GET, method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
@ -141,13 +179,13 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber, @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_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort, @RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams, @RequestParam final MultiValueMap<String, String> filterCriteria,
final HttpServletRequest request) { final HttpServletRequest request) {
// at least current user must have read access for specified entity type within its own institution // at least current user must have read access for specified entity type within its own institution
checkReadPrivilege(institutionId); checkReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString()); final FilterMap filterMap = new FilterMap(filterCriteria, request.getQueryString());
populateFilterMap(filterMap, institutionId, sort); populateFilterMap(filterMap, institutionId, sort);
return this.paginationService.getPage( return this.paginationService.getPage(
@ -176,6 +214,28 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
// * GET (names) // * 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( @RequestMapping(
path = API.NAMES_PATH_SEGMENT, path = API.NAMES_PATH_SEGMENT,
method = RequestMethod.GET, method = RequestMethod.GET,
@ -186,13 +246,13 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
name = API.PARAM_INSTITUTION_ID, name = API.PARAM_INSTITUTION_ID,
required = true, required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@RequestParam final MultiValueMap<String, String> allRequestParams, @RequestParam final MultiValueMap<String, String> filterCriteria,
final HttpServletRequest request) { final HttpServletRequest request) {
// at least current user must have read access for specified entity type within its own institution // at least current user must have read access for specified entity type within its own institution
checkReadPrivilege(institutionId); 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, // 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 // 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<T extends Entity, M extends Entity> {
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
public T create( public T create(
@RequestParam final MultiValueMap<String, String> allRequestParams, @RequestParam final MultiValueMap<String, String> formParams,
@RequestParam( @RequestParam(
name = API.PARAM_INSTITUTION_ID, name = API.PARAM_INSTITUTION_ID,
required = true, required = true,
@ -295,7 +355,7 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
// check write privilege for requested institution and concrete entityType // check write privilege for requested institution and concrete entityType
this.checkWritePrivilege(institutionId); 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)); .putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
final M requestModel = this.createNew(postMap); final M requestModel = this.createNew(postMap);

View file

@ -1,4 +1,4 @@
spring.profiles.include=dev-ws,dev-gui spring.profiles.include=dev-ws
sebserver.test.property=This is the development Setup sebserver.test.property=This is the development Setup

View file

@ -40,7 +40,14 @@ sebserver.webservice.distributed=false
sebserver.webservice.http.external.scheme=https sebserver.webservice.http.external.scheme=https
sebserver.webservice.http.external.servername= sebserver.webservice.http.external.servername=
sebserver.webservice.http.external.port= 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 ### webservice API
sebserver.webservice.api.admin.clientId=guiClient sebserver.webservice.api.admin.clientId=guiClient

View file

@ -1,5 +1,5 @@
spring.application.name=SEB Server spring.application.name=SEB Server
spring.profiles.active=ws,gui,dev spring.profiles.active=ws,dev
sebserver.version=@sebserver-version@ sebserver.version=@sebserver-version@
########################################################## ##########################################################