use of org.ehrbase.response.ehrscape.CompositionFormat in project ehrbase by ehrbase.
the class CustomMethodSecurityExpressionRoot method templateHandling.
/**
* Handles template ID extraction of specific payload.
* <p>
* Payload will be a response body string, in case of @PostAuthorize.
* <p>
* Payload will be request body string, or already deserialized object (e.g. EhrStatus), in case of @PreAuthorize.
* @param type Object type of scope
* @param payload Payload object, either request's input or response's output
* @param contentType Content type from the scope
* @param requestMap ABAC request attribute map to add the result
* @param authType Pre- or PostAuthorize, determines payload style (string or object)
*/
private void templateHandling(String type, Object payload, String contentType, Map<String, Object> requestMap, String authType) {
switch(type) {
case BaseController.EHR:
throw new IllegalArgumentException("ABAC: Unsupported configuration: Can't set template ID for EHR type.");
case BaseController.EHR_STATUS:
throw new IllegalArgumentException("ABAC: Unsupported configuration: Can't set template ID for EHR_STATUS type.");
case BaseController.COMPOSITION:
String content = "";
if (authType.equals(POST)) {
// @PostAuthorize gives a ResponseEntity type for "returnObject", so payload is of that type
if (((ResponseEntity) payload).hasBody()) {
Object body = ((ResponseEntity) payload).getBody();
// can have "No content" here (even with some data in the body) if the compo was (logically) deleted
if (((ResponseEntity<?>) payload).getStatusCode().equals(HttpStatus.NO_CONTENT)) {
if (body instanceof Map) {
Object error = ((Map<?, ?>) body).get("error");
if (error != null) {
if (((String) error).contains("delet")) {
// composition was deleted, so nothing to check here, skip
break;
}
}
}
throw new InternalServerException("ABAC: Unexpected empty response from composition reuquest");
}
if (body instanceof OriginalVersionResponseData) {
// case of versioned_composition --> fast path, because template is easy to get
if (((OriginalVersionResponseData<?>) body).getData() instanceof Composition) {
String template = Objects.requireNonNull(((Composition) ((OriginalVersionResponseData<?>) body).getData()).getArchetypeDetails().getTemplateId()).getValue();
requestMap.put(TEMPLATE, template);
// special case, so done here, exit
break;
}
} else if (body instanceof String) {
content = (String) body;
} else {
throw new InternalServerException("ABAC: unexpected composition payload object");
}
} else {
throw new InternalServerException("ABAC: unexpected empty response body");
}
} else if (authType.equals(PRE)) {
try {
// try if this is the Delete composition case. Payload would contain the UUID of the compo.
ObjectVersionId versionId = new ObjectVersionId((String) payload);
UUID compositionUid = UUID.fromString(versionId.getRoot().getValue());
Optional<CompositionDto> compoDto = compositionService.retrieve(compositionUid, null);
if (compoDto.isPresent()) {
Composition c = compoDto.get().getComposition();
requestMap.put(TEMPLATE, c.getArchetypeDetails().getTemplateId().getValue());
// special case, so done here, exit
break;
} else {
throw new InternalServerException("ABAC: unexpected empty response from composition delete");
}
} catch (IllegalArgumentException e) {
// if not an UUID, the payload is a composition itself so continue
content = (String) payload;
}
} else {
throw new InternalServerException("ABAC: invalid auth type given.");
}
String templateId;
if (MediaType.parseMediaType(contentType).isCompatibleWith(MediaType.APPLICATION_JSON)) {
templateId = compositionService.getTemplateIdFromInputComposition(content, CompositionFormat.JSON);
} else if (MediaType.parseMediaType(contentType).isCompatibleWith(MediaType.APPLICATION_XML)) {
templateId = compositionService.getTemplateIdFromInputComposition(content, CompositionFormat.XML);
} else {
throw new IllegalArgumentException("ABAC: Only JSON and XML composition are supported.");
}
requestMap.put(TEMPLATE, templateId);
break;
case BaseController.CONTRIBUTION:
CompositionFormat format;
if (MediaType.parseMediaType(contentType).isCompatibleWith(MediaType.APPLICATION_JSON)) {
format = CompositionFormat.JSON;
} else if (MediaType.parseMediaType(contentType).isCompatibleWith(MediaType.APPLICATION_XML)) {
format = CompositionFormat.XML;
} else {
throw new IllegalArgumentException("ABAC: Only JSON and XML composition are supported.");
}
if (payload instanceof String) {
Set<String> templates = contributionService.getListOfTemplates((String) payload, format);
requestMap.put(TEMPLATE, templates);
break;
} else {
throw new InternalServerException("ABAC: invalid POST contribution payload.");
}
case BaseController.QUERY:
// special case of type QUERY, where multiple subjects are possible
if (payload instanceof Map) {
if (((Map<?, ?>) payload).containsKey(AuditVariables.TEMPLATE_PATH)) {
Set<String> templates = (Set) ((Map<?, ?>) payload).get(AuditVariables.TEMPLATE_PATH);
Set<String> templateSet = new HashSet<>(templates);
// put result set into the requestMap and exit
requestMap.put(TEMPLATE, templateSet);
break;
} else {
throw new InternalServerException("ABAC: AQL audit template data unavailable.");
}
} else {
throw new InternalServerException("ABAC: AQL audit template data malformed.");
}
default:
throw new InternalServerException("ABAC: Invalid type given from Pre- or PostAuthorize");
}
}
use of org.ehrbase.response.ehrscape.CompositionFormat in project ehrbase by ehrbase.
the class OpenehrContributionController method buildContributionResponseData.
private <T extends ContributionResponseData> Optional<InternalResponse<T>> buildContributionResponseData(UUID contributionId, UUID ehrId, String accept, URI uri, List<String> headerList, Supplier<T> factory) {
// create either CompositionResponseData or null (means no body, only headers incl. link to resource), via lambda request
T minimalOrRepresentation = factory.get();
// do minimal scope steps
// create and supplement headers with data depending on which headers are requested
HttpHeaders respHeaders = new HttpHeaders();
for (String header : headerList) {
switch(header) {
case LOCATION:
respHeaders.setLocation(uri);
break;
case ETAG:
respHeaders.setETag("\"" + contributionId + "\"");
break;
case LAST_MODIFIED:
// TODO should be VERSION.commit_audit.time_committed.value which is not implemented yet - mock for now
respHeaders.setLastModified(123124442);
break;
default:
}
}
// if response data objects was created as "representation" do all task from wider scope, too
if (minimalOrRepresentation != null) {
// when this "if" is true the following casting can be executed and data manipulated by reference (handled by temporary variable)
ContributionResponseData objByReference = minimalOrRepresentation;
// retrieve contribution
Optional<ContributionDto> contribution = contributionService.getContribution(ehrId, contributionId);
// set all response field according to retrieved contribution
objByReference.setUid(new HierObjectId(contributionId.toString()));
List<ObjectRef<ObjectVersionId>> refs = new LinkedList<>();
contribution.get().getObjectReferences().forEach((id, type) -> refs.add(new ObjectRef<>(new ObjectVersionId(id), "local", type)));
objByReference.setVersions(refs);
objByReference.setAudit(contribution.get().getAuditDetails());
CompositionFormat format = extractCompositionFormat(accept);
// finally set last header
if (format.equals(CompositionFormat.XML)) {
respHeaders.setContentType(MediaType.APPLICATION_XML);
} else if (format.equals(CompositionFormat.JSON) || format.equals(CompositionFormat.FLAT) || format.equals(CompositionFormat.ECISFLAT) || format.equals(CompositionFormat.RAW)) {
respHeaders.setContentType(MediaType.APPLICATION_JSON);
} else {
throw new NotAcceptableException("Wrong Accept header in request");
}
}
return Optional.of(new InternalResponse<>(minimalOrRepresentation, respHeaders));
}
use of org.ehrbase.response.ehrscape.CompositionFormat in project ehrbase by ehrbase.
the class OpenehrCompositionController method updateComposition.
@PutMapping("/{ehr_id}/composition/{versioned_object_uid}")
// checkAbacPre /-Post attributes (type, subject, payload, content type)
@PreAuthorize("checkAbacPre(@openehrCompositionController.COMPOSITION, " + "@ehrService.getSubjectExtRef(#ehrIdString), #composition, #contentType)")
@Override
public ResponseEntity updateComposition(String openehrVersion, @RequestHeader(value = "openEHR-AUDIT_DETAILS", required = false) String openehrAuditDetails, @RequestHeader(value = CONTENT_TYPE, required = false) String contentType, @RequestHeader(value = ACCEPT, required = false) String accept, @RequestHeader(value = PREFER, required = false) String prefer, @RequestHeader(value = IF_MATCH) String ifMatch, @PathVariable(value = "ehr_id") String ehrIdString, @PathVariable(value = "versioned_object_uid") String versionedObjectUidString, @RequestBody String composition, HttpServletRequest request) {
UUID ehrId = getEhrUuid(ehrIdString);
UUID versionedObjectUid = getCompositionVersionedObjectUidString(versionedObjectUidString);
CompositionFormat compositionFormat = extractCompositionFormat(contentType);
// check if composition ID path variable is valid
compositionService.exists(versionedObjectUid);
// If the If-Match is not the latest latest existing version, throw error
if (!((versionedObjectUid + "::" + compositionService.getServerConfig().getNodename() + "::" + compositionService.getLastVersionNumber(extractVersionedObjectUidFromVersionUid(versionedObjectUid.toString()))).equals(ifMatch))) {
throw new PreconditionFailedException("If-Match header does not match latest existing version");
}
// If body already contains a composition uid it must match the {versioned_object_uid} in request url
Optional<String> inputUuid = Optional.ofNullable(compositionService.getUidFromInputComposition(composition, compositionFormat));
inputUuid.ifPresent(id -> {
// TODO it is further unclear what exactly the REST spec's "match" means, see: https://github.com/openEHR/specifications-ITS-REST/issues/83
if (!versionedObjectUid.equals(extractVersionedObjectUidFromVersionUid(id))) {
throw new PreconditionFailedException("UUID from input must match given versioned_object_uid in request URL");
}
});
// variable to overload with more specific object if requested
Optional<InternalResponse<CompositionResponseData>> respData = Optional.empty();
try {
Composition compoObj = compositionService.buildComposition(composition, compositionFormat, null);
// TODO should have EHR as parameter and check for existence as precondition - see EHR-245 (no direct EHR access in this controller)
// ifMatch header has to be tested for correctness already above
Optional<CompositionDto> dtoOptional = compositionService.update(ehrId, new ObjectVersionId(ifMatch), compoObj);
var compositionVersionUid = dtoOptional.orElseThrow(() -> new InternalServerException("Failed to create composition")).getComposition().getUid().toString();
var uri = URI.create(this.encodePath(getBaseEnvLinkURL() + "/rest/openehr/v1/ehr/" + ehrId.toString() + "/composition/" + compositionVersionUid));
List<String> headerList = Arrays.asList(LOCATION, ETAG, // whatever is required by REST spec - CONTENT_TYPE only needed for 200, so handled separately
LAST_MODIFIED);
UUID compositionId = extractVersionedObjectUidFromVersionUid(compositionVersionUid);
if (RETURN_REPRESENTATION.equals(prefer)) {
// both options extract needed info from versionUid
respData = buildCompositionResponseData(compositionId, extractVersionFromVersionUid(compositionVersionUid), accept, uri, headerList, () -> new CompositionResponseData(null, null));
} else {
// "minimal" is default fallback
respData = buildCompositionResponseData(compositionId, extractVersionFromVersionUid(compositionVersionUid), accept, uri, headerList, () -> null);
}
// Enriches request attributes with current compositionId for later audit processing
request.setAttribute(OpenEhrAuditInterceptor.EHR_ID_ATTRIBUTE, Collections.singleton(ehrId));
request.setAttribute(CompositionAuditInterceptor.COMPOSITION_ID_ATTRIBUTE, compositionId);
} catch (ObjectNotFoundException e) {
// composition not found
return ResponseEntity.notFound().build();
}
// returns 200 with body + headers, 204 only with headers or 500 error depending on what processing above yields
return respData.map(i -> Optional.ofNullable(i.getResponseData()).map(StructuredString::getValue).map(j -> ResponseEntity.ok().headers(i.getHeaders()).body(j)).orElse(ResponseEntity.noContent().headers(i.getHeaders()).build())).orElse(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
}
use of org.ehrbase.response.ehrscape.CompositionFormat in project ehrbase by ehrbase.
the class EhrController method buildEhrResponseData.
private Optional<EhrResponseData> buildEhrResponseData(UUID ehrId, Action create, String contentType) {
// check for valid format header to produce content accordingly
CompositionFormat format;
if (contentType == null) {
// assign default if no header was set
format = CompositionFormat.RAW;
} else {
// if header was set process it
MediaType mediaType = MediaType.parseMediaType(contentType);
if (mediaType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
format = CompositionFormat.RAW;
} else if (mediaType.isCompatibleWith(MediaType.APPLICATION_XML)) {
format = CompositionFormat.XML;
} else {
throw new InvalidApiParameterException("Wrong Content-Type header in request");
}
}
// Optional<EhrStatusDto> ehrStatus = ehrService.getEhrStatus(ehrId, CompositionFormat.FLAT); // older, keep until rework of formatting
Optional<EhrStatusDto> ehrStatus = ehrService.getEhrStatusEhrScape(ehrId, format);
if (!ehrStatus.isPresent()) {
return Optional.empty();
}
EhrResponseData responseData = new EhrResponseData();
responseData.setAction(create);
responseData.setEhrId(ehrId);
responseData.setEhrStatus(ehrStatus.get());
RestHref url = new RestHref();
// TODO: use config file or alike to set the basic api path
url.setUrl(getBaseEnvLinkURL() + "/rest/ecis/v1/ehr/" + responseData.getEhrId());
Meta meta = new Meta();
meta.setHref(url);
responseData.setMeta(meta);
return Optional.of(responseData);
}
use of org.ehrbase.response.ehrscape.CompositionFormat in project ehrbase by ehrbase.
the class BaseController method extractCompositionFormat.
/**
* Extracts the {@link CompositionFormat} from the REST request's input {@link MediaType} style
* content type header string.
*
* @param contentType String representation of REST request's input {@link MediaType} style
* content type header
* @return {@link CompositionFormat} expressing the content type
* @throws NotAcceptableException when content type is not supported or input is invalid
*/
protected CompositionFormat extractCompositionFormat(String contentType) {
final CompositionFormat compositionFormat;
MediaType mediaType = resolveContentType(contentType);
if (mediaType.isCompatibleWith(MediaType.APPLICATION_XML)) {
compositionFormat = CompositionFormat.XML;
} else if (mediaType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
compositionFormat = CompositionFormat.JSON;
} else {
throw new NotAcceptableException("Only compositions in XML or JSON are supported at the moment");
}
return compositionFormat;
}
Aggregations