Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Only submit applications that are in draft or returned status #24

Merged
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,75 @@
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;

@ApplicationScoped
public class SubmitApplicationService {

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

@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 {
inderps marked this conversation as resolved.
Show resolved Hide resolved
var application = remsApplicationsApi.apiApplicationsApplicationIdGet(id, remsApiKey, userId);

if (!application.getApplicationApplicant().getUserid().equals(userId)) {
brunopacheco1 marked this conversation as resolved.
Show resolved Hide resolved
inderps marked this conversation as resolved.
Show resolved Hide resolved
throw new UserNotApplicantException(id, userId);
}

if (!application.getApplicationState().equals(ApplicationStateEnum.DRAFT) && !application.getApplicationState().equals(ApplicationStateEnum.RETURNED)) {
brunopacheco1 marked this conversation as resolved.
Show resolved Hide resolved
brunopacheco1 marked this conversation as resolved.
Show resolved Hide resolved
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'
brunopacheco1 marked this conversation as resolved.
Show resolved Hide resolved
/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() {
inderps marked this conversation as resolved.
Show resolved Hide resolved
given()
.auth()
.oauth2(getAccessToken("alice"))
.when()
.post("/api/v1/applications/12345/submit")
.then()
.statusCode(404);
}

@Test
void submitApplication_when_not_applicant() {
brunopacheco1 marked this conversation as resolved.
Show resolved Hide resolved
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