diff --git a/backend/web/src/main/java/org/opencdmp/configurations/OpenAPISecurityConfig.java b/backend/web/src/main/java/org/opencdmp/configurations/OpenAPISecurityConfig.java new file mode 100644 index 000000000..c424d430b --- /dev/null +++ b/backend/web/src/main/java/org/opencdmp/configurations/OpenAPISecurityConfig.java @@ -0,0 +1,49 @@ +package org.opencdmp.configurations; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenAPISecurityConfig { + + @Value("${keycloak-client.serverUrl}") + String authServerUrl; + @Value("${keycloak-client.realm}") + String realm; + + private static final String OAUTH_SCHEME_NAME = "oAuth_security_schema"; + + @Bean + public OpenAPI openAPI() { + Components oauthComponent = new Components(); + oauthComponent.addSecuritySchemes(OAUTH_SCHEME_NAME, createOAuthScheme()); + OpenAPI openAPI = new OpenAPI(); + openAPI.components(oauthComponent); + openAPI.addSecurityItem(new SecurityRequirement().addList(OAUTH_SCHEME_NAME)); + return openAPI; + } + + private SecurityScheme createOAuthScheme() { + OAuthFlows flows = createOAuthFlows(); + return new SecurityScheme().type(SecurityScheme.Type.OAUTH2) + .flows(flows); + } + + private OAuthFlows createOAuthFlows() { + OAuthFlow flow = createAuthorizationCodeFlow(); + return new OAuthFlows().implicit(flow); + } + + private OAuthFlow createAuthorizationCodeFlow() { + return new OAuthFlow() + .authorizationUrl(authServerUrl + "/realms/" + realm + "/protocol/openid-connect/auth") + .scopes(new Scopes().addString("read_access", "read data") + .addString("write_access", "modify data")); + } + +} diff --git a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java index 136f30030..0591d8c15 100644 --- a/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java +++ b/backend/web/src/main/java/org/opencdmp/controllers/DescriptionController.java @@ -11,6 +11,7 @@ import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.validation.ValidationFilterAnnotation; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.xml.bind.JAXBException; @@ -108,6 +109,7 @@ public class DescriptionController { @PostMapping("public/query") @Operation(summary = "Query public descriptions") + @Hidden public QueryResult publicQuery(@RequestBody DescriptionLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", PublicDescription.class.getSimpleName()); @@ -123,6 +125,7 @@ public class DescriptionController { @GetMapping("public/{id}") @Operation(summary = "Fetch a specific public description by id") + @Hidden public PublicDescription publicGet(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException { logger.debug(new MapLogEntry("retrieving" + PublicDescription.class.getSimpleName()).And("id", id).And("fields", fieldSet)); fieldSet = this.fieldSetExpanderService.expand(fieldSet); @@ -143,7 +146,7 @@ public class DescriptionController { } @PostMapping("query") - @Operation(summary = "Query all descriptions") + @Operation(summary = "Query all descriptions", description = SwaggerHelpers.endpoint_query) public QueryResult query(@RequestBody DescriptionLookup lookup) throws MyApplicationException, MyForbiddenException { logger.debug("querying {}", Description.class.getSimpleName()); @@ -216,6 +219,7 @@ public class DescriptionController { @PostMapping("get-description-section-permissions") @Operation(summary = "Fetch the section specific user permissions") + @Hidden @ValidationFilterAnnotation(validator = DescriptionSectionPermissionResolver.DescriptionSectionPermissionResolverPersistValidator.ValidatorName, argumentName = "model") public Map> getDescriptionSectionPermissions(@RequestBody DescriptionSectionPermissionResolver model) { logger.debug(new MapLogEntry("persisting" + Description.class.getSimpleName()).And("model", model)); @@ -230,6 +234,7 @@ public class DescriptionController { @GetMapping("validate") @Operation(summary = "Validate if a description is ready for finalization by id") + @Hidden public List validate(@RequestParam("descriptionIds") List descriptionIds) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException { logger.debug(new MapLogEntry("validating" + Description.class.getSimpleName()).And("descriptionIds", descriptionIds)); @@ -331,4 +336,125 @@ public class DescriptionController { )); return response; } + + protected static class SwaggerHelpers { + static final String endpoint_query = + """ + This endpoint is used to fetch all the available descriptions.
+ It also allows to restrict the results using a query object passed in the request body.
+ Let's explore the options this object gives us. + + ### General query parameters: + +
    +
  • page: + This is an object controlling the pagination of the results. It contains two properties. +
  • +
      +
    • offset: + How many records to omit. +
    • +
    • size: + How many records to include in each page. +
    • +
    +
+ + For example, if we want the third page, and our pages to contain 15 elements, we would pass the following object: + + ```JSON + { + "offset": 30, + "size": 15 + } + ``` + +
    +
  • order: + This is an object controlling the ordering of the results. + It contains a list of strings called items with the names of the properties to use. +
    If the name of the property is prefixed with a '-', the ordering direction is DESC. Otherwise, it is ASC. +
  • +
+ + For example, if we wanted to order based on the field 'createdAt' in descending order, we would pass the following object: + + ```JSON + { + "items": [ + "-createdAt" + ], + } + ``` + +
    +
  • metadata: + This is an object containing metadata for the request. There is only one available option. +
      +
    • countAll: + If this is set to true, the count property included in the response will account for all the records regardless the pagination, + with all the rest of filtering options applied of course. + Otherwise, if it is set to false or not present, only the returned results will be counted. +
      The first option is useful for the UI clients to calculate how many result pages are available. +
    • +
    +
  • +
  • project: + This is an object controlling the data projection of the results. + It contains a list of strings called fields with the names of the properties to project. +
    You can also include properties that are deeper in the object tree by prefixing them with dots. +
  • +
+ + ### Description specific query parameters: + +
    +
  • like: + If there is a like parameter present in the query, only the description entities that include the contents of the parameter either in their labels or the descriptions will be in the response. +
  • +
  • ids: + This is a list and contains the ids we want to include in the response.
    If empty, every record is included. +
  • +
  • excludedIds: + This is a list and contains the ids we want to exclude from the response.
    If empty, no record gets excluded. +
  • +
  • isActive: + This is a list and determines which records we want to include in the response, based on if they are deleted or not. + This filter works like this. If we want to view only the active records we pass [1] and for only the deleted records we pass [0]. +
    If not present or if we pass [0,1], every record is included. +
  • +
  • statuses: + This is a list and determines which records we want to include in the response, based on their status. + The status can be Draft, Finalized or Canceled. We add 0, 1 or 2 to the list respectively. +
    If not present, every record is included. +
  • +
  • createdAfter: + This is a date and determines which records we want to include in the response, based on their creation date. + Specifically, only the records created after the given date are included. +
    If not present, every record is included. +
  • +
  • createdBefore: + This is a date and determines which records we want to include in the response, based on their creation date. + Specifically, only the records created before the given date are included. +
    If not present, every record is included. +
  • +
  • finalizedAfter: + This is a date and determines which records we want to include in the response, based on their finalization date. + Specifically, only the records finalized after the given date are included. +
    If not present, every record is included. +
  • +
  • finalizedBefore: + This is a date and determines which records we want to include in the response, based on their finalization date. + Specifically, only the records finalized before the given date are included. +
    If not present, every record is included. +
  • +
+ """; + + static final String endpoint_ = + """ + + """; + } + } diff --git a/backend/web/src/main/resources/config/swagger.yml b/backend/web/src/main/resources/config/swagger.yml index f944323e0..6bd94de52 100644 --- a/backend/web/src/main/resources/config/swagger.yml +++ b/backend/web/src/main/resources/config/swagger.yml @@ -4,15 +4,17 @@ springdoc: enabled: true groupConfigs: - group: public-api - displayName: Public API + displayName: Legacy packagesToScan: org.opencdmp.controllers.publicapi pathsToMatch: "/api/public/dmps/**, /api/public/datasets/**" - group: internal-api - displayName: Internal API + displayName: Current packagesToScan: org.opencdmp.controllers packagesToExclude: org.opencdmp.controllers.publicapi pathsToMatch: "/api/dmp/**, /api/description/**" - swagger-ui: + swaggerUi: enabled: true useRootPath: true - docExpansion: none \ No newline at end of file + docExpansion: none + oauth: + clientId: dmp_swagger \ No newline at end of file