DataController.java
package se.jobtechdev.personaldatagateway.api.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.context.request.NativeWebRequest;
import se.jobtechdev.personaldatagateway.api.exception.ApiException;
import se.jobtechdev.personaldatagateway.api.generated.api.DataApi;
import se.jobtechdev.personaldatagateway.api.generated.entities.ClientEntity;
import se.jobtechdev.personaldatagateway.api.generated.entities.SharingEntity;
import se.jobtechdev.personaldatagateway.api.generated.model.DataAccess;
import se.jobtechdev.personaldatagateway.api.service.ClientService;
import se.jobtechdev.personaldatagateway.api.service.DataService;
import se.jobtechdev.personaldatagateway.api.service.SharingService;
import se.jobtechdev.personaldatagateway.api.util.*;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Predicate;
import static se.jobtechdev.personaldatagateway.api.util.ControllerUtil.earlyExit;
import static se.jobtechdev.personaldatagateway.api.util.ControllerUtil.throwApiExceptionOnAbsentValue;
@Controller
public class DataController implements DataApi {
private final String baseUrl;
private final NativeWebRequest request;
private final ClientService clientService;
private final SharingService sharingService;
private final DataService dataService;
@Autowired
public DataController(
@Value("${pdg-api.base-url}") String baseUrl,
NativeWebRequest request,
ClientService clientService,
SharingService sharingService,
DataService dataService) {
this.baseUrl = baseUrl;
this.request = request;
this.clientService = clientService;
this.sharingService = sharingService;
this.dataService = dataService;
}
static Predicate<ClientEntity> clientInequalityCheck(ClientEntity client1) {
return client2 -> client2 != null && !client2.equals(client1);
}
static Predicate<ZonedDateTime> isTimestampNullOrPassedCheck() {
return timestamp -> timestamp != null && timestamp.isBefore(TimeProvider.now());
}
static SharingEntity getSharingAndAssertClientIfNotPublic(SharingService sharingService, ClientService clientService, UUID dataId, String authKey) {
final var sharing =
throwApiExceptionOnAbsentValue(
sharingService.getSharingById(dataId),
HttpStatus.NOT_FOUND,
String.format(
"Failed to handle GET data request - Could not find sharing by id: %s", dataId));
final var sharingClient = sharing.getClientEntity();
if (null != sharingClient) {
throwApiExceptionOnAbsentValue(
Optional.ofNullable(authKey),
HttpStatus.UNAUTHORIZED,
"An authorization key has to be provided in the X-Auth-Key header for non-public data access");
byte[] decodedAuthKey;
try {
decodedAuthKey = AuthUtil.decodeAuthKey(authKey);
} catch (Exception e) {
throw new ApiException(
ProblemDetailsFactory.createProblemDetails(
HttpStatus.UNAUTHORIZED, "The authorization key has to be a base64 encoded value"));
}
final var authKeyHash = AuthUtil.hashDecodedAuthKey(decodedAuthKey);
final var authClient =
throwApiExceptionOnAbsentValue(
clientService.getClientByKeyHash(authKeyHash),
HttpStatus.UNAUTHORIZED,
"Failed to handle GET data request - Could not find any client associated with the"
+ " provided auth key");
earlyExit(
sharingClient,
clientInequalityCheck(authClient),
HttpStatus.FORBIDDEN,
String.format(
"Failed to handle GET data request - Authenticated client does not match"
+ " the sharing's associated client"));
}
return sharing;
}
static SharingEntity assertSharingValidity(SharingEntity sharing) {
earlyExit(sharing.getRevoked(), Objects::nonNull, HttpStatus.FORBIDDEN, null);
earlyExit(sharing.getExpires(), isTimestampNullOrPassedCheck(), HttpStatus.FORBIDDEN, null);
return sharing;
}
@Override
@CrossOrigin(origins = "*")
public ResponseEntity<byte[]> getData(UUID dataId, String authKey) {
final var sharing =
getSharingAndAssertClientIfNotPublic(sharingService, clientService, dataId, authKey);
final var validSharing = assertSharingValidity(sharing);
final var datasource = sharing.getDatasourceEntity();
final var datasourceId = datasource.getId();
final var contextPath = request.getContextPath();
final var datasourceLink =
Link.of(baseUrl + contextPath + "/datasources/" + datasourceId, "datasource");
final var dataWrapper =
dataService.retrieveData(
sharing.getDatasourceEntity(),
sharing.getPersonEntity().getId(),
request.getHeader(HttpHeaders.ACCEPT));
sharingService.createSharingAccess(validSharing);
final var headers = LinkHeaderUtil.createHeaders(List.of(datasourceLink), new Class<?>[]{UUID.class, String.class}, dataId, authKey);
return ResponseEntity.status(dataWrapper.status())
.contentType(dataWrapper.contentType())
.headers(headers)
.body(dataWrapper.body());
}
@Override
public ResponseEntity<DataAccess> getDataAccess(UUID dataId, UUID accessId) {
final var sharingAccess =
throwApiExceptionOnAbsentValue(
sharingService.getSharingAccessById(accessId),
HttpStatus.NOT_FOUND,
String.format("Could not find data access by id: %s", accessId));
earlyExit(
sharingAccess.getSharingEntity().getId(),
UuidProvider.generateInequalityCheckingLambda(dataId),
HttpStatus.NOT_FOUND,
String.format(
"Could not find data access by dataId: %s and accessId: %s",
dataId.toString(), accessId.toString()));
final var response =
ResponseFactory.createDataAccess(sharingAccess);
final var headers = LinkHeaderUtil.createHeaders(new Class<?>[]{UUID.class, UUID.class}, dataId, accessId);
return ResponseEntity.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.headers(headers)
.body(response);
}
@Override
public ResponseEntity<List<DataAccess>> getDataAccesses(
UUID dataId, Integer offset, Integer limit) {
final var sharing =
throwApiExceptionOnAbsentValue(
sharingService.getSharingById(dataId),
HttpStatus.NOT_FOUND,
String.format("Could not find sharing by id: %s", dataId));
final var pageable = OffsetBasedPageRequest.of(offset, limit);
final var sharingAccesses = sharingService.getAllSharingAccessesBySharing(sharing, pageable);
final var response =
sharingAccesses.stream()
.map(
ResponseFactory::createDataAccess)
.toList();
final var headers =
LinkHeaderUtil.createHeadersWithPagination(
sharingAccesses, new Class<?>[]{UUID.class, Integer.class, Integer.class}, dataId, (int) pageable.getOffset(), pageable.getPageSize());
return ResponseEntity.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.headers(headers)
.body(response);
}
}