Skip to content

Commit

Permalink
Merge pull request #24 from GenomicDataInfrastructure/issue-5/only-su…
Browse files Browse the repository at this point in the history
…bmit-draft-and-returned-applications

feat: Only submit applications that are in draft or returned status
  • Loading branch information
brunopacheco1 authored Mar 22, 2024
2 parents ec1e01c + 6141161 commit 7c745ee
Show file tree
Hide file tree
Showing 18 changed files with 439 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public Response saveApplicationFormsAndDuosV1(String id, SaveFormsAndDuos saveFo
}

@Override
public Response submitApplicationV1(String id) {
public Response submitApplicationV1(Long id) {
submitApplicationService.submitApplication(id);
return Response.noContent().build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2024 PNED G.I.E.
//
// SPDX-License-Identifier: Apache-2.0
package io.github.genomicdatainfrastructure.daam.exceptions;

public class ApplicationNotFoundException extends RuntimeException {

private static final String MESSAGE = "Application %s not found";

public ApplicationNotFoundException(Long applicationId) {
super(MESSAGE.formatted(applicationId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2024 PNED G.I.E.
//
// SPDX-License-Identifier: Apache-2.0
package io.github.genomicdatainfrastructure.daam.exceptions;

public class ApplicationNotInCorrectStateException extends RuntimeException {

private static final String MESSAGE = "Application %s is not in correct state: %s";

public ApplicationNotInCorrectStateException(Long id, String state) {
super(MESSAGE.formatted(id, state));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2024 PNED G.I.E.
//
// SPDX-License-Identifier: Apache-2.0
package io.github.genomicdatainfrastructure.daam.exceptions;

public class UserNotApplicantException extends RuntimeException {

private static final String MESSAGE = "User %s is not an applicant for application %s";

public UserNotApplicantException(Long applicationId, String userId) {
super(MESSAGE.formatted(userId, applicationId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2024 PNED G.I.E.
//
// SPDX-License-Identifier: Apache-2.0
package io.github.genomicdatainfrastructure.daam.mappers;
import io.github.genomicdatainfrastructure.daam.exceptions.ApplicationNotFoundException;
import io.github.genomicdatainfrastructure.daam.model.ErrorResponse;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.ext.Provider;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;

@Provider
public class ApplicationNotFoundExceptionMapper implements ExceptionMapper<ApplicationNotFoundException> {

@Override
public Response toResponse(ApplicationNotFoundException exception) {
ErrorResponse errorResponse = new ErrorResponse(
"Application Not Found",
Response.Status.NOT_FOUND.getStatusCode(),
exception.getMessage()
);

return Response
.status(Response.Status.NOT_FOUND)
.entity(errorResponse)
.type(MediaType.APPLICATION_JSON)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2024 PNED G.I.E.
//
// SPDX-License-Identifier: Apache-2.0
package io.github.genomicdatainfrastructure.daam.mappers;

import io.github.genomicdatainfrastructure.daam.exceptions.ApplicationNotFoundException;
import io.github.genomicdatainfrastructure.daam.exceptions.ApplicationNotInCorrectStateException;
import io.github.genomicdatainfrastructure.daam.model.ErrorResponse;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

@Provider
public class ApplicationNotInCorrectStateExceptionMapper implements ExceptionMapper<ApplicationNotInCorrectStateException> {
@Override
public Response toResponse(ApplicationNotInCorrectStateException exception) {
ErrorResponse errorResponse = new ErrorResponse(
"Application Not In Correct State",
Response.Status.PRECONDITION_REQUIRED.getStatusCode(),
exception.getMessage()
);

return Response
.status(Response.Status.PRECONDITION_REQUIRED)
.entity(errorResponse)
.type(MediaType.APPLICATION_JSON)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2024 PNED G.I.E.
//
// SPDX-License-Identifier: Apache-2.0
package io.github.genomicdatainfrastructure.daam.mappers;

import io.github.genomicdatainfrastructure.daam.exceptions.UserNotApplicantException;
import io.github.genomicdatainfrastructure.daam.model.ErrorResponse;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

@Provider
public class UserNotApplicantExceptionMapper implements ExceptionMapper<UserNotApplicantException> {
@Override
public Response toResponse(UserNotApplicantException exception) {

ErrorResponse errorResponse = new ErrorResponse(
"User Not Applicant",
Response.Status.FORBIDDEN.getStatusCode(),
exception.getMessage()
);

return Response
.status(Response.Status.FORBIDDEN)
.entity(errorResponse)
.type(MediaType.APPLICATION_JSON)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public ListedApplication parse(ApplicationOverview applicationOverview) {
return ListedApplication.builder()
.id(applicationOverview.getApplicationId().toString())
.title(applicationOverview.getApplicationExternalId())
.currentState(applicationOverview.getApplicationState())
.currentState(applicationOverview.getApplicationState().value())
.stateChangedAt(applicationOverview.getApplicationLastActivity())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,80 @@
package io.github.genomicdatainfrastructure.daam.services;

import static io.github.genomicdatainfrastructure.daam.security.PostAuthenticationFilter.USER_ID_CLAIM;

import io.github.genomicdatainfrastructure.daam.exceptions.ApplicationNotFoundException;
import io.github.genomicdatainfrastructure.daam.exceptions.ApplicationNotInCorrectStateException;
import io.github.genomicdatainfrastructure.daam.exceptions.UserNotApplicantException;
import io.github.genomicdatainfrastructure.daam.remote.rems.api.RemsApplicationCommandApi;
import io.github.genomicdatainfrastructure.daam.remote.rems.api.RemsApplicationsApi;
import io.github.genomicdatainfrastructure.daam.remote.rems.model.SubmitApplicationCommand;
import io.github.genomicdatainfrastructure.daam.remote.rems.model.ApplicationOverview.ApplicationStateEnum;
import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.ws.rs.WebApplicationException;

import java.util.Set;

@ApplicationScoped
public class SubmitApplicationService {

private final SecurityIdentity identity;
private final String remsApiKey;
private final RemsApplicationCommandApi remsApplicationCommandApi;
private final RemsApplicationsApi remsApplicationsApi;

private static final Set<ApplicationStateEnum> STATES_FORBIDDEN_FOR_SUBMISSION = Set.of(ApplicationStateEnum.DRAFT, ApplicationStateEnum.RETURNED);

@Inject
public SubmitApplicationService(
@ConfigProperty(name = "quarkus.rest-client.rems_yaml.api-key") String remsApiKey,
SecurityIdentity identity,
@RestClient RemsApplicationCommandApi applicationsApi
@RestClient RemsApplicationCommandApi applicationCommandApi,
@RestClient RemsApplicationsApi applicationsApi
) {
this.remsApiKey = remsApiKey;
this.identity = identity;
this.remsApplicationCommandApi = applicationsApi;
this.remsApplicationCommandApi = applicationCommandApi;
this.remsApplicationsApi = applicationsApi;
}

public void submitApplication(String id) {
public void submitApplication(Long id) {
var principal = (OidcJwtCallerPrincipal) identity.getPrincipal();
String userId = principal.getClaim(USER_ID_CLAIM);

checkApplication(id, userId);

SubmitApplicationCommand command = SubmitApplicationCommand.builder()
.applicationId(Long.valueOf(id))
.applicationId(id)
.build();

remsApplicationCommandApi.apiApplicationsSubmitPost(command, remsApiKey, userId);
}

private void checkApplication(Long id, String userId) {
try {
var application = remsApplicationsApi.apiApplicationsApplicationIdGet(id, remsApiKey, userId);

if (!application.getApplicationApplicant().getUserid().equals(userId)) {
throw new UserNotApplicantException(id, userId);
}

if (!STATES_FORBIDDEN_FOR_SUBMISSION.contains(application.getApplicationState())) {
throw new ApplicationNotInCorrectStateException(id, application.getApplicationState().value());
}

} catch (WebApplicationException e) {
if (e.getResponse().getStatus() == 404) {
throw new ApplicationNotFoundException(id);
}

throw e;
}

}

}
33 changes: 32 additions & 1 deletion src/main/openapi/daam.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ paths:
description: ID of application to submit
required: true
schema:
type: string
type: integer
format: int64
responses:
"204":
description: Successful Response (no content)
Expand All @@ -124,6 +125,25 @@ paths:
type: array
items:
$ref: "#/components/schemas/ValidationWarnings"
"404":
description: Application not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"403":
description: Application does not belong to applicant
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"428":
description: Application not in submittable state
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"

security:
- daam_auth:
- write:applications
Expand Down Expand Up @@ -823,3 +843,14 @@ components:
title: dataset ids
items:
type: string
ErrorResponse:
properties:
title:
type: string
title: Error title
status:
type: integer
title: Error status
detail:
type: string
title: Error detail
38 changes: 38 additions & 0 deletions src/main/openapi/rems.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,36 @@ paths:
type: array
items:
$ref: '#/components/schemas/ApplicationOverview'
/api/applications/{application-id}:
get:
tags:
- rems-applications
summary: 'Get application details (roles: logged-in)'
parameters:
- name: x-rems-api-key
in: header
description: REMS API-Key (optional for UI, required for API)
schema:
type: string
- name: x-rems-user-id
in: header
description: user (optional for UI, required for API). This can be a REMS internal or an external user identity attribute (specified in config.edn).
schema:
type: string
- name: application-id
in: path
description: Application id
required: true
schema:
type: integer
format: int64
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/ApplicationOverview'
/api/applications/create:
post:
tags:
Expand Down Expand Up @@ -274,6 +304,14 @@ components:
format: date-time
application/state:
type: string
enum:
- application.state/draft
- application.state/closed
- application.state/approved
- application.state/returned
- application.state/rejected
- application.state/revoked
- application.state/submitted
application/copied-to:
type: array
items:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import io.github.genomicdatainfrastructure.daam.model.CreateApplication;
import org.junit.jupiter.api.Test;

import static org.hamcrest.Matchers.equalTo;
import io.quarkus.test.junit.QuarkusTest;
import static io.restassured.RestAssured.given;
import io.quarkus.test.keycloak.client.KeycloakTestClient;
Expand Down Expand Up @@ -52,6 +51,40 @@ void submitApplication_when_authenticated() {
.statusCode(204);
}

@Test
void submitApplication_when_application_not_found() {
given()
.auth()
.oauth2(getAccessToken("alice"))
.when()
.post("/api/v1/applications/12345/submit")
.then()
.statusCode(404);
}

@Test
void submitApplication_when_not_applicant() {
given()
.auth()
.oauth2(getAccessToken("jdoe"))
.when()
.post("/api/v1/applications/1/submit")
.then()
.statusCode(403);
}

@Test
void submitApplication_when_application_not_in_submittable_state() {
given()
.auth()
.oauth2(getAccessToken("alice"))
.when()
.post("/api/v1/applications/2/submit")
.then()
.statusCode(428);
}


private String getAccessToken(String userName) {
return keycloakClient.getAccessToken(userName);
}
Expand Down
Loading

0 comments on commit 7c745ee

Please sign in to comment.