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

Feature 3789 user can cancel job #3799

Merged
merged 9 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// SPDX-License-Identifier: MIT
package com.mercedesbenz.sechub.domain.administration;

import static com.mercedesbenz.sechub.sharedkernel.security.APIConstants.API_ADMINISTRATION;
import static com.mercedesbenz.sechub.sharedkernel.security.APIConstants.API_ANONYMOUS;
import static com.mercedesbenz.sechub.sharedkernel.security.APIConstants.*;

public class AdministrationAPIConstants {

Expand Down Expand Up @@ -126,4 +125,8 @@ private AdministrationAPIConstants() {
public static final String API_FETCH_NEW_API_TOKEN_BY_ONE_WAY_TOKEN = API_ANONYMOUS + "apitoken";
public static final String API_REQUEST_NEW_APITOKEN = API_ANONYMOUS + "refresh/apitoken/{emailAddress}";

/* +-----------------------------------------------------------------------+ */
/* +............................... Jobs ..................................+ */
/* +-----------------------------------------------------------------------+ */
public static final String API_USER_CANCEL_JOB = API_MANAGEMENT + "jobs/{jobUUID}/cancel";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
package com.mercedesbenz.sechub.domain.administration.job;

import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import com.mercedesbenz.sechub.domain.administration.project.Project;
import com.mercedesbenz.sechub.domain.administration.user.User;
import com.mercedesbenz.sechub.domain.administration.user.UserRepository;
import com.mercedesbenz.sechub.sharedkernel.Step;
import com.mercedesbenz.sechub.sharedkernel.error.InternalServerErrorException;
import com.mercedesbenz.sechub.sharedkernel.error.NotFoundException;
import com.mercedesbenz.sechub.sharedkernel.logging.AuditLogService;
import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage;
import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageFactory;
Expand All @@ -22,47 +24,82 @@
import com.mercedesbenz.sechub.sharedkernel.messaging.MessageDataKeys;
import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID;
import com.mercedesbenz.sechub.sharedkernel.usecases.job.UseCaseAdminCancelsJob;
import com.mercedesbenz.sechub.sharedkernel.usecases.job.UseCaseUserCancelsJob;
import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion;

@Service
public class JobCancelService {

private static final Logger LOG = LoggerFactory.getLogger(JobCancelService.class);
private final AuditLogService auditLogService;
private final UserInputAssertion userInputAssertion;
private final DomainMessageService eventBusService;
private final JobInformationRepository jobInformationRepository;
private final UserRepository userRepository;

public JobCancelService(AuditLogService auditLogService, UserInputAssertion userInputAssertion, DomainMessageService eventBusService,
JobInformationRepository jobInformationRepository, UserRepository userRepository) {
this.auditLogService = auditLogService;
this.userInputAssertion = userInputAssertion;
this.eventBusService = eventBusService;
this.jobInformationRepository = jobInformationRepository;
this.userRepository = userRepository;
}

@Autowired
AuditLogService auditLogService;
@UseCaseAdminCancelsJob(@Step(number = 2, name = "Cancel job", description = "Will trigger event that job cancel requested"))
public void cancelJob(UUID jobUUID) {
userInputAssertion.assertIsValidJobUUID(jobUUID);

@Autowired
UserInputAssertion assertion;
auditLogService.log("Requested cancellation of job {}", jobUUID);

@Autowired
DomainMessageService eventBusService;
JobMessage message = buildMessage(jobUUID);

@Autowired
JobInformationRepository repository;
/* trigger event */
informCancelJobRequested(message);
}

@Autowired
UserRepository userRepository;
@UseCaseUserCancelsJob(@Step(number = 2, name = "Cancel job", description = "Will trigger event that job cancel requested"))
public void userCancelJob(UUID jobUUID, String userId) {
userInputAssertion.assertIsValidJobUUID(jobUUID);
userInputAssertion.assertIsValidUserId(userId);

@Validated
@UseCaseAdminCancelsJob(@Step(number = 2, name = "Cancel job", description = "Will trigger event that job cancel requested"))
public void cancelJob(UUID jobUUID) {
assertion.assertIsValidJobUUID(jobUUID);

auditLogService.log("Requested cancellation of job {}", jobUUID);
auditLogService.log("User {} requested cancellation of job {}", userId, jobUUID);
assertUserAllowedCancelJob(jobUUID, userId);

JobMessage message = buildMessage(jobUUID);

/* trigger event */
informCancelJobRequested(message);
}

private void assertUserAllowedCancelJob(UUID jobUUID, String userId) {
lorriborri marked this conversation as resolved.
Show resolved Hide resolved
JobInformation jobInfo = jobInformationRepository.findById(jobUUID).orElseThrow(() -> {
LOG.debug("Job not found: {}", jobUUID);
return new NotFoundException("Job not found: " + jobUUID);
});

User user = userRepository.findOrFailUser(userId);
Set<Project> projects = user.getProjects();

if (projects == null) {
LOG.debug("Projects for user {} are null.", userId);
throw new InternalServerErrorException("Projects fore user %s are null.".formatted(userId));
}

boolean isForbidden = projects.stream().noneMatch(project -> project.getId().equals(jobInfo.getProjectId()));

if (isForbidden) {
LOG.debug("User {} is not allowed to cancel job {}", userId, jobUUID);
throw new NotFoundException("Job not found: " + jobUUID);
}
}

private JobMessage buildMessage(UUID jobUUID) {
JobMessage message = new JobMessage();

message.setJobUUID(jobUUID);

Optional<JobInformation> optJobInfo = repository.findById(jobUUID);
Optional<JobInformation> optJobInfo = jobInformationRepository.findById(jobUUID);
if (optJobInfo.isEmpty()) {
LOG.warn("Did not found job information, so not able to resolve owner email address");
return message;
Expand Down Expand Up @@ -91,5 +128,4 @@ private void informCancelJobRequested(JobMessage message) {

eventBusService.sendAsynchron(infoRequest);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
package com.mercedesbenz.sechub.domain.administration.job;

import java.util.UUID;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import com.mercedesbenz.sechub.domain.administration.AdministrationAPIConstants;
import com.mercedesbenz.sechub.sharedkernel.Step;
import com.mercedesbenz.sechub.sharedkernel.security.RoleConstants;
import com.mercedesbenz.sechub.sharedkernel.security.UserContextService;
import com.mercedesbenz.sechub.sharedkernel.usecases.job.UseCaseUserCancelsJob;

import jakarta.annotation.security.RolesAllowed;

@RestController
public class JobRestController {
hamidonos marked this conversation as resolved.
Show resolved Hide resolved

private final UserContextService userContextService;
private final JobCancelService jobCancelService;

public JobRestController(UserContextService userContextService, JobCancelService jobCancelService) {
this.userContextService = userContextService;
this.jobCancelService = jobCancelService;
}

@UseCaseUserCancelsJob(@Step(number = 1, name = "Rest call", description = "Triggers job cancellation request, owners of project will be informed", needsRestDoc = true))
@PostMapping(path = AdministrationAPIConstants.API_USER_CANCEL_JOB, produces = { MediaType.APPLICATION_JSON_VALUE })
@RolesAllowed({ RoleConstants.ROLE_USER, RoleConstants.ROLE_SUPERADMIN, RoleConstants.ROLE_OWNER })
@ResponseStatus(HttpStatus.NO_CONTENT)
public void userCancelJob(@PathVariable(name = "jobUUID") UUID jobUUID) {
lorriborri marked this conversation as resolved.
Show resolved Hide resolved
String userId = userContextService.getUserId();
jobCancelService.userCancelJob(jobUUID, userId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT
package com.mercedesbenz.sechub.domain.administration.job;

import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.*;

import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import org.junit.Test;

import com.mercedesbenz.sechub.domain.administration.project.Project;
import com.mercedesbenz.sechub.domain.administration.user.User;
import com.mercedesbenz.sechub.domain.administration.user.UserRepository;
import com.mercedesbenz.sechub.sharedkernel.error.NotFoundException;
import com.mercedesbenz.sechub.sharedkernel.logging.AuditLogService;
import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService;
import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion;

public class JobCancelServiceTest {
lorriborri marked this conversation as resolved.
Show resolved Hide resolved

private static final AuditLogService auditLogService = mock();
private static final UserInputAssertion userInputAssertion = mock();
private static final DomainMessageService eventBusService = mock();
private static final JobInformationRepository jobInformationRepository = mock();
private static final UserRepository userRepository = mock();
private static final JobCancelService serviceToTest = new JobCancelService(auditLogService, userInputAssertion, eventBusService, jobInformationRepository,
userRepository);

@Test
public void userCancelJob_receives_not_found_exception_when_job_not_found() {
/* prepare */
UUID jobUUID = UUID.randomUUID();
String userId = "user1";
when(jobInformationRepository.findById(jobUUID)).thenReturn(Optional.empty());

/* execute + test */
assertThatThrownBy(() -> serviceToTest.userCancelJob(jobUUID, userId)).isInstanceOf(NotFoundException.class);
verify(eventBusService, never()).sendAsynchron(any());

}

@Test
public void userCancelJob_receives_not_found_exception_when_project_not_assigned() {
/* prepare */
UUID jobUUID = UUID.randomUUID();
String userId = "user1";
String projectId = "project1";
String otherProjectId = "project2";

JobInformation jobInformation = mock(JobInformation.class);
when(jobInformation.getProjectId()).thenReturn(projectId);
Project project = mock(Project.class);
when(project.getId()).thenReturn(otherProjectId);

User user = mock(User.class);
when(user.getProjects()).thenReturn(Set.of(project));

when(userRepository.findOrFailUser(userId)).thenReturn(user);
when(jobInformationRepository.findById(jobUUID)).thenReturn(Optional.of(jobInformation));

/* execute + test */
assertThatThrownBy(() -> serviceToTest.userCancelJob(jobUUID, userId)).isInstanceOf(NotFoundException.class);
verify(eventBusService, never()).sendAsynchron(any());
}

@Test
public void userCancelJob_receives_no_exception_when_job_found_and_authorized() {
/* prepare */
UUID jobUUID = UUID.randomUUID();
String userId = "user1";
String projectId = "project1";

JobInformation jobInformation = mock(JobInformation.class);
when(jobInformation.getProjectId()).thenReturn(projectId);
Project project = mock(Project.class);
when(project.getId()).thenReturn(projectId);

User user = mock(User.class);
when(user.getProjects()).thenReturn(Set.of(project));

when(userRepository.findOrFailUser(userId)).thenReturn(user);
when(jobInformationRepository.findById(jobUUID)).thenReturn(Optional.of(jobInformation));

/* execute + test */
assertThatCode(() -> serviceToTest.userCancelJob(jobUUID, userId)).doesNotThrowAnyException();
verify(eventBusService, times(1)).sendAsynchron(any());
}

}
Loading
Loading