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

VKT(Frontend&Backend): OPHKIOS-108 Tutkinnon vastaanottajien listaus #737

Draft
wants to merge 11 commits into
base: feature/OPHKIOS-108
Choose a base branch
from
Draft
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
Expand Up @@ -6,6 +6,7 @@
import fi.oph.vkt.api.dto.clerk.ClerkExamEventDTO;
import fi.oph.vkt.api.dto.clerk.ClerkExamEventListDTO;
import fi.oph.vkt.api.dto.clerk.ClerkExamEventUpdateDTO;
import fi.oph.vkt.model.type.ExamLevel;
import fi.oph.vkt.service.ClerkExamEventService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
Expand Down Expand Up @@ -33,7 +34,7 @@ public class ClerkExamEventController {
@GetMapping
@Operation(tags = TAG_EXAM_EVENT, summary = "List all exam events")
public List<ClerkExamEventListDTO> list() {
return clerkExamEventService.list();
return clerkExamEventService.list(ExamLevel.EXCELLENT);
}

@GetMapping(path = "/{examEventId:\\d+}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ public record PublicExaminerDTO(
@NonNull @NotNull String firstName,
@NonNull @NotNull List<ExamLanguage> languages,
@NonNull @NotNull List<PublicMunicipalityDTO> municipalities,
@NonNull @NotNull List<LocalDate> examDates
@NonNull @NotNull List<PublicExaminerExamDateDTO> examDates
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package fi.oph.vkt.api.dto;

import java.time.LocalDate;
import lombok.Builder;
import lombok.NonNull;

@Builder
public record PublicExaminerExamDateDTO(@NonNull LocalDate examDate, @NonNull Boolean isFull) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fi.oph.vkt.api.dto.examiner;

import lombok.Builder;
import lombok.NonNull;

@Builder
public record ExaminerDetailsCreateDTO(
@NonNull String email,
@NonNull Boolean examLanguageFinnish,
@NonNull Boolean examLanguageSwedish
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package fi.oph.vkt.api.dto.examiner;

import lombok.Builder;
import lombok.NonNull;

@Builder
public record ExaminerDetailsDTO(
@NonNull Long id,
@NonNull Integer version,
@NonNull String oid,
@NonNull String email,
@NonNull String lastName,
@NonNull String firstName,
@NonNull Boolean examLanguageFinnish,
@NonNull Boolean examLanguageSwedish
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package fi.oph.vkt.api.dto.examiner;

import lombok.Builder;
import lombok.NonNull;

@Builder
public record ExaminerDetailsInitDTO(@NonNull String oid, @NonNull String lastName, @NonNull String firstName) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package fi.oph.vkt.api.dto.examiner;

import lombok.Builder;
import lombok.NonNull;

@Builder
public record ExaminerUserDTO(@NonNull String oid) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package fi.oph.vkt.api.examiner;

import fi.oph.vkt.api.dto.examiner.ExaminerDetailsCreateDTO;
import fi.oph.vkt.api.dto.examiner.ExaminerDetailsDTO;
import fi.oph.vkt.api.dto.examiner.ExaminerDetailsInitDTO;
import fi.oph.vkt.service.ExaminerDetailsService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/api/v1/tv/{oid}", produces = MediaType.APPLICATION_JSON_VALUE)
public class ExaminerDetailsController {

private static final String TAG_EXAMINER = "Examiner details API";

@Resource
private ExaminerDetailsService examinerDetailsService;

@GetMapping
@Operation(tags = TAG_EXAMINER, summary = "Get examiner details")
public ExaminerDetailsDTO getExaminerDetails(@PathVariable("oid") String oid) {
return examinerDetailsService.getExaminer(oid);
}

@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(tags = TAG_EXAMINER, summary = "Create examiner")
public ExaminerDetailsDTO createExaminer(
@PathVariable("oid") String oid,
@RequestBody ExaminerDetailsCreateDTO examinerDetailsCreateDTO
) {
return examinerDetailsService.createExaminer(oid, examinerDetailsCreateDTO);
}

@GetMapping(path = "/init")
@Operation(tags = TAG_EXAMINER, summary = "Get examiner personal data needed for initializing examiner details")
public ExaminerDetailsInitDTO getInitialExaminerDetails(@PathVariable("oid") String oid) {
return examinerDetailsService.getInitialExaminerPersonalData(oid);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package fi.oph.vkt.api.examiner;

import fi.oph.vkt.api.dto.clerk.ClerkExamEventListDTO;
import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/api/v1/tv/{oid}/examEvent", produces = MediaType.APPLICATION_JSON_VALUE)
public class ExaminerExamEventController {

private static final String TAG_EXAMINER_EXAM_EVENT = "Exam event API for examiners";

@GetMapping
@Operation(tags = TAG_EXAMINER_EXAM_EVENT, summary = "List all exam events")
public List<ClerkExamEventListDTO> list(@PathVariable String oid) {
return List.of();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package fi.oph.vkt.api.examiner;

import fi.oph.vkt.api.dto.examiner.ExaminerUserDTO;
import org.springframework.http.MediaType;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/api/v1/tv/user", produces = MediaType.APPLICATION_JSON_VALUE)
public class ExaminerUserController {

@GetMapping(path = "")
public ExaminerUserDTO currentExaminerUser() {
final String oid = SecurityContextHolder.getContext().getAuthentication().getName();
return ExaminerUserDTO.builder().oid(oid).build();
}
}
34 changes: 34 additions & 0 deletions backend/vkt/src/main/java/fi/oph/vkt/config/AppConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
import fi.oph.vkt.service.email.sender.EmailSender;
import fi.oph.vkt.service.email.sender.EmailSenderNoOp;
import fi.oph.vkt.service.email.sender.EmailSenderViestintapalvelu;
import fi.oph.vkt.service.onr.OnrOperationApi;
import fi.oph.vkt.service.onr.OnrOperationApiImpl;
import fi.oph.vkt.service.onr.mock.MockOnrOperationApiImpl;
import fi.oph.vkt.util.UUIDSource;
import fi.vm.sade.javautils.nio.cas.CasClient;
import fi.vm.sade.javautils.nio.cas.CasClientBuilder;
import fi.vm.sade.javautils.nio.cas.CasConfig;
import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -143,6 +149,34 @@ public AwsCredentialsProvider defaultAwsCredentialsProvider() {
return ContainerCredentialsProvider.builder().build();
}

@Bean
@Profile("dev")
public OnrOperationApi onrOperationApiMock() {
LOG.warn("OnrOperationApiMock in use");
return new MockOnrOperationApiImpl();
}

@Bean
@Profile("!dev")
public OnrOperationApi onrOperationApiImpl(
@Value("${app.onr.service-url}") final String onrServiceUrl,
@Value("${cas.url}") final String casUrl,
@Value("${app.onr.cas.username}") final String casUsername,
@Value("${app.onr.cas.password}") final String casPassword
) {
LOG.info("onrServiceUrl: {}", onrServiceUrl);
final CasConfig casConfig = CasConfig.SpringSessionCasConfig(
casUsername,
casPassword,
casUrl,
onrServiceUrl,
Constants.CALLER_ID,
Constants.CALLER_ID
);
final CasClient casClient = CasClientBuilder.build(casConfig);
return new OnrOperationApiImpl(casClient, onrServiceUrl);
}

private static WebClient.Builder webClientBuilderWithCallerId(final String connectionProviderName) {
ConnectionProvider connectionProvider = ConnectionProvider
.builder(connectionProviderName)
Expand Down
4 changes: 4 additions & 0 deletions backend/vkt/src/main/java/fi/oph/vkt/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ public class Constants {
public static final String EMAIL_SENDER_NAME = "Valtionhallinnon kielitutkinnot | Opetushallitus";
public static final String SERVICENAME = "vkt";
public static final String APP_ROLE = "APP_VKT";
// TODO Get actual role
public static final String APP_ADMIN_ROLE = "APP_VKT_PAAKAYTTAJA";
// TODO Get actual role
public static final String APP_TV_ROLE = "APP_VKT_TUTKINNON_VASTAANOTTAJA";

// For now, no containers are run in untuva during nighttime
// Daily at 9:00
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,33 @@
import fi.oph.vkt.config.Constants;
import fi.oph.vkt.util.OpintopolkuCasAuthenticationFilter;
import fi.vm.sade.javautils.kayttooikeusclient.OphUserDetailsServiceImpl;
import java.util.Map;
import org.apereo.cas.client.session.HashMapBackedSessionMappingStorage;
import org.apereo.cas.client.session.SessionMappingStorage;
import org.apereo.cas.client.session.SingleSignOutFilter;
import org.apereo.cas.client.validation.Cas20ProxyTicketValidator;
import org.apereo.cas.client.validation.TicketValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
Expand Down Expand Up @@ -150,12 +158,40 @@ public SecurityFilterChain filterChain(
.build();
}

private static boolean hasRole(final Authentication authentication, final String role) {
return authentication
.getAuthorities()
.stream()
.anyMatch((grantedAuthority -> grantedAuthority.getAuthority().equals("ROLE_" + role)));
}

public static HttpSecurity commonConfig(final HttpSecurity httpSecurity) throws Exception {
final AuthorizationManager<RequestAuthorizationContext> examinerApiAuthorizationManager =
(
(authenticationSupplier, object) -> {
Authentication authentication = authenticationSupplier.get();
if (hasRole(authentication, Constants.APP_ROLE)) {
return new AuthorizationDecision(true);
} else if (hasRole(authentication, Constants.APP_TV_ROLE)) {
final Map<String, String> requestVariables = object.getVariables();
final String expectedOid = requestVariables.get("oid");
if (expectedOid != null && expectedOid.equals(authentication.getName())) {
return new AuthorizationDecision(true);
}
}
return new AuthorizationDecision(false);
}
);

return configCsrf(httpSecurity)
.authorizeHttpRequests(registry ->
registry
.requestMatchers("/api/v1/clerk/**", "/virkailija/**", "/virkailija")
.hasRole(Constants.APP_ROLE)
.requestMatchers("/api/v1/tv/{oid}/**")
.access(examinerApiAuthorizationManager)
.requestMatchers("/api/v1/tv/**", "/tv/**", "/tv")
.hasAnyRole(Constants.APP_ADMIN_ROLE, Constants.APP_TV_ROLE)
.requestMatchers("/", "/**")
.permitAll()
.anyRequest()
Expand Down
Loading
Loading