Skip to content

Commit

Permalink
invenio-damap: enable connection from InvenioRDM
Browse files Browse the repository at this point in the history
  • Loading branch information
SotosTsepe committed Jul 29, 2024
1 parent f6236ac commit 29d93ab
Show file tree
Hide file tree
Showing 5 changed files with 593 additions and 0 deletions.
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>io.quarkus</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package org.damap.base.rest.invenio_damap;

import io.smallrye.jwt.auth.principal.JWTParser;
import io.smallrye.jwt.auth.principal.ParseException;

import io.quarkus.security.ForbiddenException;
import io.quarkus.security.UnauthorizedException;

import java.util.List;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.json.JsonObject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.jwt.JsonWebToken;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.damap.base.repo.AccessRepo;
import org.damap.base.rest.dmp.domain.DmpDO;
import org.damap.base.rest.dmp.domain.DmpListItemDO;
import org.damap.base.rest.dmp.service.DmpService;
import org.damap.base.rest.madmp.dto.Dataset;
import org.damap.base.security.SecurityService;
import org.damap.base.validation.AccessValidator;

import lombok.extern.jbosslog.JBossLog;

@Path("/api/madmps")
@RequestScoped
@Produces(MediaType.APPLICATION_JSON)
@JBossLog
public class InvenioDAMAPResource {

@Inject
public InvenioDAMAPResource(SecurityService securityService, AccessValidator accessValidator, AccessRepo accessRepo,
DmpService dmpService, InvenioDAMAPService invenioDAMAPService) {
this.securityService = securityService;
this.accessValidator = accessValidator;
this.accessRepo = accessRepo;
this.dmpService = dmpService;
this.invenioDAMAPService = invenioDAMAPService;
}

SecurityService securityService;
AccessValidator accessValidator;
AccessRepo accessRepo;
DmpService dmpService;
InvenioDAMAPService invenioDAMAPService;

@ConfigProperty(name = "invenio.shared-secret")
String sharedSecret;

@Inject
JWTParser parser;

/*
* Maybe make it configurable?
* Could be more elaborate but should suffice for PoC
* Could also go into the {@link org.damap.base.security.SecurityService},
* but keeping everything together for PoC
*
*/
private JsonWebToken validateAuthHeader(HttpHeaders headers) {
String jwt_token = headers.getHeaderString("X-Auth");

if (jwt_token != null && !jwt_token.isEmpty()) {
try {
JsonWebToken jwt = parser.verify(jwt_token, sharedSecret);
log.info("Smallrye jwt: " + jwt);
return jwt;
} catch (ParseException e) {
return null;
}
}
return null; // No Authorization header or not in the correct format
}

// Checks provided jwt.
private JsonWebToken checkIfUserIsAuthorized(HttpHeaders headers) {

JsonWebToken jwt = validateAuthHeader(headers);
boolean authorized = jwt != null;

if (!authorized) {
throw new UnauthorizedException("User unauthorized.");
}
return jwt;
}

// Resolves user identity based on the DMPs and returns dmp list and list of
// identifiers if successful.
private SimpleEntry<List<DmpListItemDO>, List<String>> resolveDmpsAndIds(JsonObject identifiers) {
List<DmpListItemDO> personDmpList = null;
List<String> matchingIdentifiers = new ArrayList<>();

for (String key : identifiers.keySet()) {
String identifier = identifiers.getString(key);
List<DmpListItemDO> dmps = dmpService.getDmpListByPersonId(identifier);

// Check if returned DMPs is null or the list is empty.
// If yes, no resolving can take place, move to the next identifier.
if (dmps == null || dmps.isEmpty())
continue;

// Here dmps would contain at least one DMP so keep track of the current
// iterated identifier.
matchingIdentifiers.add(identifier);

// Keep track of the 1st list that matches at least one DMP.
// Then compare it with other non-null or non-empty lists.
if (personDmpList == null)
personDmpList = dmps;

// If a DMP list is different than the previous one, a different identity was
// resolved.
else if (!personDmpList.equals(dmps))
throw new UnauthorizedException("Mismatch in resolved identities.");
}

// Check if DMP list is null. This means that no resolving was possible.
if (personDmpList == null)
throw new NotFoundException("No valid identities were found.");

return new SimpleEntry<>(personDmpList, matchingIdentifiers);
}

@GET
public List<DmpListItemDO> getDmpListByPerson(@Context HttpHeaders headers) {
JsonWebToken jwt = checkIfUserIsAuthorized(headers);
JsonObject invenioDamap = jwt.getClaim("invenio-damap");
JsonObject identifiers = invenioDamap.getJsonObject("identifiers");

SimpleEntry<List<DmpListItemDO>, List<String>> result = resolveDmpsAndIds(identifiers);
List<DmpListItemDO> dmps = result.getKey();
return dmps;
}

@POST
@Consumes(MediaType.APPLICATION_JSON)
public DmpDO addDataSetToDmp(@Context HttpHeaders headers, JsonObject payload) {

JsonWebToken jwt = checkIfUserIsAuthorized(headers);
JsonObject invenioDamap = jwt.getClaim("invenio-damap");
JsonObject identifiers = invenioDamap.getJsonObject("identifiers");

SimpleEntry<List<DmpListItemDO>, List<String>> result = resolveDmpsAndIds(identifiers);
List<String> personIds = result.getValue();
String strDmpId = payload.getString("dmp_id");
long dmpId;

try {
dmpId = Long.parseLong(strDmpId);
} catch (NumberFormatException e) {
throw new BadRequestException("Invalid dmp_id format.");
}

// Deserialize the dataset JSON object into a Dataset instance
ObjectMapper objMap = new ObjectMapper();
Dataset dataset;

try {
dataset = objMap.readValue(payload.getJsonObject("dataset").toString(), Dataset.class);
} catch (Exception e) {
throw new BadRequestException("Invalid dataset format");
}

// Assuming all IDs provided fetch the same DMPs, check permissions.
for (String personId : personIds) {
if (!accessValidator.canEditDmp(dmpId, personId))
throw new ForbiddenException("Person " + personId + "Not authorized to access dmp with id " + dmpId);
}
log.info("Add dataset to dmp with id: " + dmpId);
return invenioDAMAPService.addDataSetToDMP(dmpId, dataset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.damap.base.rest.invenio_damap;

import java.text.MessageFormat;
import java.util.Date;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import org.damap.base.rest.dmp.domain.DatasetDO;
import org.damap.base.rest.dmp.domain.DmpDO;
import org.damap.base.rest.dmp.service.DmpService;
import org.damap.base.rest.madmp.dto.Dataset;
import org.damap.base.rest.version.VersionDO;
import org.damap.base.rest.version.VersionService;

@ApplicationScoped
public class InvenioDAMAPService {

protected DmpService dmpService;
protected VersionService versionService;

@Inject
InvenioDAMAPService(DmpService dmpService, VersionService versionService) {
this.dmpService = dmpService;
this.versionService = versionService;
}

@Transactional
public DmpDO addDataSetToDMP(long dmpId, Dataset dataset) {

DmpDO dmpDO = dmpService.getDmpById(dmpId);
var datasetDO = dmpDO.getDatasets().stream().filter(ds -> {
var localIdentifier = ds.getDatasetId();
var externalIdentifier = dataset.getDatasetId();

if (localIdentifier == null || externalIdentifier == null) {
return false;
}

return localIdentifier.getIdentifier() != null && externalIdentifier.getIdentifier() != null
&& localIdentifier.getType() != null && externalIdentifier.getType() != null
&& localIdentifier.getIdentifier().equals(externalIdentifier.getIdentifier())
&& localIdentifier.getType().toString().equalsIgnoreCase(externalIdentifier.getType().name());
}).findFirst().orElse(new DatasetDO());

datasetDO = InvenioDamapResourceMapper.mapMaDMPDatasetToDatasetDO(dataset, datasetDO, dmpDO);

dmpDO.getDatasets().add(datasetDO);
dmpDO = dmpService.update(dmpDO);

VersionDO version = new VersionDO();
version.setDmpId(dmpId);
version.setVersionName(MessageFormat.format("Added dataset `{0}` from remote datasource", dataset.getTitle()));
version.setVersionDate(new Date());
versionService.create(version);

return dmpDO;
}
}
Loading

0 comments on commit 29d93ab

Please sign in to comment.