@Test public void partyInfoValidateThrowsException() { String url = "http: PublicKey myKey = PublicKey.from("myKey".getBytes()); PublicKey recipientKey = PublicKey.from("recipientKey".getBytes()); String message = "I love sparrows"; byte[] payload = message.getBytes(); Recipient recipient = Recipient.of(recipientKey, url); Set<Recipient> recipientList = Collections.singleton(recipient); PartyInfo partyInfo = new PartyInfo(url, recipientList, Collections.emptySet()); when(partyInfoParser.from(payload)).thenReturn(partyInfo); when(enclave.defaultPublicKey()).thenReturn(myKey); when(partyInfoParser.to(partyInfo)).thenReturn(payload); EncodedPayload encodedPayload = mock(EncodedPayload.class); when(enclave.encryptPayload(any(byte[].class), any(PublicKey.class), anyList())).thenReturn(encodedPayload); when(payloadEncoder.encode(encodedPayload)).thenReturn(payload); WebTarget webTarget = mock(WebTarget.class); when(restClient.target(url)).thenReturn(webTarget); when(webTarget.path(anyString())).thenReturn(webTarget); Invocation.Builder invocationBuilder = mock(Invocation.Builder.class); when(webTarget.request()).thenReturn(invocationBuilder); when(invocationBuilder.post(any(Entity.class))) .thenThrow(new UncheckedIOException(new IOException("GURU meditation"))); try { partyInfoResource.partyInfo(payload, null); failBecauseExceptionWasNotThrown(SecurityException.class); } catch (SecurityException ex) { verify(partyInfoParser).from(payload); verify(enclave).defaultPublicKey(); verify(enclave).encryptPayload(any(byte[].class), any(PublicKey.class), anyList()); verify(payloadEncoder).encode(encodedPayload); verify(restClient).target(url); } }
@POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation(value = "Request public key/url of other nodes") @ApiResponses({ @ApiResponse( code = 200, message = "Empty response if node is using remote key validation, else an encoded partyinfo containing only the local node's URL", response = byte[].class), @ApiResponse(code = 500, message = "If node is using remote key validation, indicates validation failed") }) public Response partyInfo( @ApiParam(required = true) final byte[] payload, @HeaderParam(Constants.API_VERSION_HEADER) final List<String> headers) { final PartyInfo partyInfo = partyInfoParser.from(payload); final Set<String> versions = Optional.ofNullable(headers).orElse(emptyList()).stream() .filter(Objects::nonNull) .flatMap(v -> Arrays.stream(v.split(","))) .collect(Collectors.toSet()); final NodeInfo nodeInfo = NodeInfoUtil.from(partyInfo, versions); LOGGER.debug("Received PartyInfo from {}", partyInfo.getUrl()); if (!enableKeyValidation) { LOGGER.debug("Key validation not enabled, passing PartyInfo through"); discovery.onUpdate(nodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); final PartyInfo emptyInfo = new PartyInfo(discovery.getCurrent().getUrl(), emptySet(), emptySet()); final byte[] returnData = partyInfoParser.to(emptyInfo); return Response.ok(returnData).build(); } final PublicKey localPublicKey = enclave.defaultPublicKey(); final Predicate<Recipient> isValidRecipient = r -> { try { LOGGER.debug("Validating key {} for peer {}", r.getKey(), r.getUrl()); final String dataToEncrypt = UUID.randomUUID().toString(); final EncodedPayload encodedPayload = enclave.encryptPayload( dataToEncrypt.getBytes(), localPublicKey, Arrays.asList(r.getKey())); final byte[] encodedPayloadBytes = payloadEncoder.encode(encodedPayload); try (Response response = restClient .target(r.getUrl()) .path("partyinfo") .path("validate") .request() .post(Entity.entity(encodedPayloadBytes, MediaType.APPLICATION_OCTET_STREAM))) { LOGGER.debug("Response code {} from peer {}", response.getStatus(), r.getUrl()); final String responseData = response.readEntity(String.class); final boolean isValid = Objects.equals(responseData, dataToEncrypt); if (!isValid) { LOGGER.warn( "Validation of key {} for peer {} failed. Key and peer will not be added to local partyinfo.", r.getKey(), r.getUrl()); LOGGER.debug("Response from {} was {}", r.getUrl(), responseData); } return isValid; } } catch (Exception ex) { LOGGER.debug(null, ex); return false; } }; final String partyInfoSender = partyInfo.getUrl(); final Predicate<Recipient> isSender = r -> NodeUri.create(r.getUrl()).equals(NodeUri.create(partyInfoSender)); final Set<com.quorum.tessera.partyinfo.node.Recipient> validatedSendersKeys = partyInfo.getRecipients().stream() .filter(isSender.and(isValidRecipient)) .map(r -> com.quorum.tessera.partyinfo.node.Recipient.of(r.getKey(), r.getUrl())) .collect(Collectors.toSet()); LOGGER.debug("Validated keys for peer {}: {}", partyInfoSender, validatedSendersKeys); if (validatedSendersKeys.isEmpty()) { throw new SecurityException("No validated keys found for peer " + partyInfoSender); } final NodeInfo reducedNodeInfo = NodeInfo.Builder.create() .withUrl(partyInfoSender) .withSupportedApiVersions(versions) .withRecipients(validatedSendersKeys) .build(); discovery.onUpdate(reducedNodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); return Response.ok().build(); }
PartyInfoResource { @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation(value = "Request public key/url of other nodes") @ApiResponses({ @ApiResponse( code = 200, message = "Empty response if node is using remote key validation, else an encoded partyinfo containing only the local node's URL", response = byte[].class), @ApiResponse(code = 500, message = "If node is using remote key validation, indicates validation failed") }) public Response partyInfo( @ApiParam(required = true) final byte[] payload, @HeaderParam(Constants.API_VERSION_HEADER) final List<String> headers) { final PartyInfo partyInfo = partyInfoParser.from(payload); final Set<String> versions = Optional.ofNullable(headers).orElse(emptyList()).stream() .filter(Objects::nonNull) .flatMap(v -> Arrays.stream(v.split(","))) .collect(Collectors.toSet()); final NodeInfo nodeInfo = NodeInfoUtil.from(partyInfo, versions); LOGGER.debug("Received PartyInfo from {}", partyInfo.getUrl()); if (!enableKeyValidation) { LOGGER.debug("Key validation not enabled, passing PartyInfo through"); discovery.onUpdate(nodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); final PartyInfo emptyInfo = new PartyInfo(discovery.getCurrent().getUrl(), emptySet(), emptySet()); final byte[] returnData = partyInfoParser.to(emptyInfo); return Response.ok(returnData).build(); } final PublicKey localPublicKey = enclave.defaultPublicKey(); final Predicate<Recipient> isValidRecipient = r -> { try { LOGGER.debug("Validating key {} for peer {}", r.getKey(), r.getUrl()); final String dataToEncrypt = UUID.randomUUID().toString(); final EncodedPayload encodedPayload = enclave.encryptPayload( dataToEncrypt.getBytes(), localPublicKey, Arrays.asList(r.getKey())); final byte[] encodedPayloadBytes = payloadEncoder.encode(encodedPayload); try (Response response = restClient .target(r.getUrl()) .path("partyinfo") .path("validate") .request() .post(Entity.entity(encodedPayloadBytes, MediaType.APPLICATION_OCTET_STREAM))) { LOGGER.debug("Response code {} from peer {}", response.getStatus(), r.getUrl()); final String responseData = response.readEntity(String.class); final boolean isValid = Objects.equals(responseData, dataToEncrypt); if (!isValid) { LOGGER.warn( "Validation of key {} for peer {} failed. Key and peer will not be added to local partyinfo.", r.getKey(), r.getUrl()); LOGGER.debug("Response from {} was {}", r.getUrl(), responseData); } return isValid; } } catch (Exception ex) { LOGGER.debug(null, ex); return false; } }; final String partyInfoSender = partyInfo.getUrl(); final Predicate<Recipient> isSender = r -> NodeUri.create(r.getUrl()).equals(NodeUri.create(partyInfoSender)); final Set<com.quorum.tessera.partyinfo.node.Recipient> validatedSendersKeys = partyInfo.getRecipients().stream() .filter(isSender.and(isValidRecipient)) .map(r -> com.quorum.tessera.partyinfo.node.Recipient.of(r.getKey(), r.getUrl())) .collect(Collectors.toSet()); LOGGER.debug("Validated keys for peer {}: {}", partyInfoSender, validatedSendersKeys); if (validatedSendersKeys.isEmpty()) { throw new SecurityException("No validated keys found for peer " + partyInfoSender); } final NodeInfo reducedNodeInfo = NodeInfo.Builder.create() .withUrl(partyInfoSender) .withSupportedApiVersions(versions) .withRecipients(validatedSendersKeys) .build(); discovery.onUpdate(reducedNodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); return Response.ok().build(); } }
PartyInfoResource { @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation(value = "Request public key/url of other nodes") @ApiResponses({ @ApiResponse( code = 200, message = "Empty response if node is using remote key validation, else an encoded partyinfo containing only the local node's URL", response = byte[].class), @ApiResponse(code = 500, message = "If node is using remote key validation, indicates validation failed") }) public Response partyInfo( @ApiParam(required = true) final byte[] payload, @HeaderParam(Constants.API_VERSION_HEADER) final List<String> headers) { final PartyInfo partyInfo = partyInfoParser.from(payload); final Set<String> versions = Optional.ofNullable(headers).orElse(emptyList()).stream() .filter(Objects::nonNull) .flatMap(v -> Arrays.stream(v.split(","))) .collect(Collectors.toSet()); final NodeInfo nodeInfo = NodeInfoUtil.from(partyInfo, versions); LOGGER.debug("Received PartyInfo from {}", partyInfo.getUrl()); if (!enableKeyValidation) { LOGGER.debug("Key validation not enabled, passing PartyInfo through"); discovery.onUpdate(nodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); final PartyInfo emptyInfo = new PartyInfo(discovery.getCurrent().getUrl(), emptySet(), emptySet()); final byte[] returnData = partyInfoParser.to(emptyInfo); return Response.ok(returnData).build(); } final PublicKey localPublicKey = enclave.defaultPublicKey(); final Predicate<Recipient> isValidRecipient = r -> { try { LOGGER.debug("Validating key {} for peer {}", r.getKey(), r.getUrl()); final String dataToEncrypt = UUID.randomUUID().toString(); final EncodedPayload encodedPayload = enclave.encryptPayload( dataToEncrypt.getBytes(), localPublicKey, Arrays.asList(r.getKey())); final byte[] encodedPayloadBytes = payloadEncoder.encode(encodedPayload); try (Response response = restClient .target(r.getUrl()) .path("partyinfo") .path("validate") .request() .post(Entity.entity(encodedPayloadBytes, MediaType.APPLICATION_OCTET_STREAM))) { LOGGER.debug("Response code {} from peer {}", response.getStatus(), r.getUrl()); final String responseData = response.readEntity(String.class); final boolean isValid = Objects.equals(responseData, dataToEncrypt); if (!isValid) { LOGGER.warn( "Validation of key {} for peer {} failed. Key and peer will not be added to local partyinfo.", r.getKey(), r.getUrl()); LOGGER.debug("Response from {} was {}", r.getUrl(), responseData); } return isValid; } } catch (Exception ex) { LOGGER.debug(null, ex); return false; } }; final String partyInfoSender = partyInfo.getUrl(); final Predicate<Recipient> isSender = r -> NodeUri.create(r.getUrl()).equals(NodeUri.create(partyInfoSender)); final Set<com.quorum.tessera.partyinfo.node.Recipient> validatedSendersKeys = partyInfo.getRecipients().stream() .filter(isSender.and(isValidRecipient)) .map(r -> com.quorum.tessera.partyinfo.node.Recipient.of(r.getKey(), r.getUrl())) .collect(Collectors.toSet()); LOGGER.debug("Validated keys for peer {}: {}", partyInfoSender, validatedSendersKeys); if (validatedSendersKeys.isEmpty()) { throw new SecurityException("No validated keys found for peer " + partyInfoSender); } final NodeInfo reducedNodeInfo = NodeInfo.Builder.create() .withUrl(partyInfoSender) .withSupportedApiVersions(versions) .withRecipients(validatedSendersKeys) .build(); discovery.onUpdate(reducedNodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); return Response.ok().build(); } PartyInfoResource(
        final Discovery discovery,
        final PartyInfoParser partyInfoParser,
        final Client restClient,
        final Enclave enclave,
        final PayloadEncoder payloadEncoder,
        final boolean enableKeyValidation,
        final PartyStore partyStore);  PartyInfoResource(
        final Discovery discovery,
        final PartyInfoParser partyInfoParser,
        final Client restClient,
        final Enclave enclave,
        final boolean enableKeyValidation); }
PartyInfoResource { @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation(value = "Request public key/url of other nodes") @ApiResponses({ @ApiResponse( code = 200, message = "Empty response if node is using remote key validation, else an encoded partyinfo containing only the local node's URL", response = byte[].class), @ApiResponse(code = 500, message = "If node is using remote key validation, indicates validation failed") }) public Response partyInfo( @ApiParam(required = true) final byte[] payload, @HeaderParam(Constants.API_VERSION_HEADER) final List<String> headers) { final PartyInfo partyInfo = partyInfoParser.from(payload); final Set<String> versions = Optional.ofNullable(headers).orElse(emptyList()).stream() .filter(Objects::nonNull) .flatMap(v -> Arrays.stream(v.split(","))) .collect(Collectors.toSet()); final NodeInfo nodeInfo = NodeInfoUtil.from(partyInfo, versions); LOGGER.debug("Received PartyInfo from {}", partyInfo.getUrl()); if (!enableKeyValidation) { LOGGER.debug("Key validation not enabled, passing PartyInfo through"); discovery.onUpdate(nodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); final PartyInfo emptyInfo = new PartyInfo(discovery.getCurrent().getUrl(), emptySet(), emptySet()); final byte[] returnData = partyInfoParser.to(emptyInfo); return Response.ok(returnData).build(); } final PublicKey localPublicKey = enclave.defaultPublicKey(); final Predicate<Recipient> isValidRecipient = r -> { try { LOGGER.debug("Validating key {} for peer {}", r.getKey(), r.getUrl()); final String dataToEncrypt = UUID.randomUUID().toString(); final EncodedPayload encodedPayload = enclave.encryptPayload( dataToEncrypt.getBytes(), localPublicKey, Arrays.asList(r.getKey())); final byte[] encodedPayloadBytes = payloadEncoder.encode(encodedPayload); try (Response response = restClient .target(r.getUrl()) .path("partyinfo") .path("validate") .request() .post(Entity.entity(encodedPayloadBytes, MediaType.APPLICATION_OCTET_STREAM))) { LOGGER.debug("Response code {} from peer {}", response.getStatus(), r.getUrl()); final String responseData = response.readEntity(String.class); final boolean isValid = Objects.equals(responseData, dataToEncrypt); if (!isValid) { LOGGER.warn( "Validation of key {} for peer {} failed. Key and peer will not be added to local partyinfo.", r.getKey(), r.getUrl()); LOGGER.debug("Response from {} was {}", r.getUrl(), responseData); } return isValid; } } catch (Exception ex) { LOGGER.debug(null, ex); return false; } }; final String partyInfoSender = partyInfo.getUrl(); final Predicate<Recipient> isSender = r -> NodeUri.create(r.getUrl()).equals(NodeUri.create(partyInfoSender)); final Set<com.quorum.tessera.partyinfo.node.Recipient> validatedSendersKeys = partyInfo.getRecipients().stream() .filter(isSender.and(isValidRecipient)) .map(r -> com.quorum.tessera.partyinfo.node.Recipient.of(r.getKey(), r.getUrl())) .collect(Collectors.toSet()); LOGGER.debug("Validated keys for peer {}: {}", partyInfoSender, validatedSendersKeys); if (validatedSendersKeys.isEmpty()) { throw new SecurityException("No validated keys found for peer " + partyInfoSender); } final NodeInfo reducedNodeInfo = NodeInfo.Builder.create() .withUrl(partyInfoSender) .withSupportedApiVersions(versions) .withRecipients(validatedSendersKeys) .build(); discovery.onUpdate(reducedNodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); return Response.ok().build(); } PartyInfoResource(
        final Discovery discovery,
        final PartyInfoParser partyInfoParser,
        final Client restClient,
        final Enclave enclave,
        final PayloadEncoder payloadEncoder,
        final boolean enableKeyValidation,
        final PartyStore partyStore);  PartyInfoResource(
        final Discovery discovery,
        final PartyInfoParser partyInfoParser,
        final Client restClient,
        final Enclave enclave,
        final boolean enableKeyValidation); @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation(value = "Request key/url of other nodes") @ApiResponses({ @ApiResponse( code = 200, message = "Empty response if node is using remote key validation, else an encoded partyinfo containing only the local node's URL", response = byte[].class), @ApiResponse(code = 500, message = "If node is using remote key validation, indicates validation failed") }) Response partyInfo(
        @ApiParam(required = true) final byte[] payload,
        @HeaderParam(Constants.API_VERSION_HEADER) final List<String> headers); @GET @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Fetch network/peer information") @ApiResponses({@ApiResponse(code = 200, message = "Peer/Network information", response = PartyInfo.class)}) Response getPartyInfo(); @POST @Path("validate") @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.TEXT_PLAIN) @ApiOperation(value = "Processes the validation request data and returns the result.") @ApiResponses({ @ApiResponse( code = 200, message = "Validation request data successfully processed and returned", response = String.class), @ApiResponse(code = 400, message = "Validation request data is not a valid UUID") }) Response validate(byte[] payloadData); }
PartyInfoResource { @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation(value = "Request public key/url of other nodes") @ApiResponses({ @ApiResponse( code = 200, message = "Empty response if node is using remote key validation, else an encoded partyinfo containing only the local node's URL", response = byte[].class), @ApiResponse(code = 500, message = "If node is using remote key validation, indicates validation failed") }) public Response partyInfo( @ApiParam(required = true) final byte[] payload, @HeaderParam(Constants.API_VERSION_HEADER) final List<String> headers) { final PartyInfo partyInfo = partyInfoParser.from(payload); final Set<String> versions = Optional.ofNullable(headers).orElse(emptyList()).stream() .filter(Objects::nonNull) .flatMap(v -> Arrays.stream(v.split(","))) .collect(Collectors.toSet()); final NodeInfo nodeInfo = NodeInfoUtil.from(partyInfo, versions); LOGGER.debug("Received PartyInfo from {}", partyInfo.getUrl()); if (!enableKeyValidation) { LOGGER.debug("Key validation not enabled, passing PartyInfo through"); discovery.onUpdate(nodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); final PartyInfo emptyInfo = new PartyInfo(discovery.getCurrent().getUrl(), emptySet(), emptySet()); final byte[] returnData = partyInfoParser.to(emptyInfo); return Response.ok(returnData).build(); } final PublicKey localPublicKey = enclave.defaultPublicKey(); final Predicate<Recipient> isValidRecipient = r -> { try { LOGGER.debug("Validating key {} for peer {}", r.getKey(), r.getUrl()); final String dataToEncrypt = UUID.randomUUID().toString(); final EncodedPayload encodedPayload = enclave.encryptPayload( dataToEncrypt.getBytes(), localPublicKey, Arrays.asList(r.getKey())); final byte[] encodedPayloadBytes = payloadEncoder.encode(encodedPayload); try (Response response = restClient .target(r.getUrl()) .path("partyinfo") .path("validate") .request() .post(Entity.entity(encodedPayloadBytes, MediaType.APPLICATION_OCTET_STREAM))) { LOGGER.debug("Response code {} from peer {}", response.getStatus(), r.getUrl()); final String responseData = response.readEntity(String.class); final boolean isValid = Objects.equals(responseData, dataToEncrypt); if (!isValid) { LOGGER.warn( "Validation of key {} for peer {} failed. Key and peer will not be added to local partyinfo.", r.getKey(), r.getUrl()); LOGGER.debug("Response from {} was {}", r.getUrl(), responseData); } return isValid; } } catch (Exception ex) { LOGGER.debug(null, ex); return false; } }; final String partyInfoSender = partyInfo.getUrl(); final Predicate<Recipient> isSender = r -> NodeUri.create(r.getUrl()).equals(NodeUri.create(partyInfoSender)); final Set<com.quorum.tessera.partyinfo.node.Recipient> validatedSendersKeys = partyInfo.getRecipients().stream() .filter(isSender.and(isValidRecipient)) .map(r -> com.quorum.tessera.partyinfo.node.Recipient.of(r.getKey(), r.getUrl())) .collect(Collectors.toSet()); LOGGER.debug("Validated keys for peer {}: {}", partyInfoSender, validatedSendersKeys); if (validatedSendersKeys.isEmpty()) { throw new SecurityException("No validated keys found for peer " + partyInfoSender); } final NodeInfo reducedNodeInfo = NodeInfo.Builder.create() .withUrl(partyInfoSender) .withSupportedApiVersions(versions) .withRecipients(validatedSendersKeys) .build(); discovery.onUpdate(reducedNodeInfo); partyInfo.getParties().stream() .map(Party::getUrl) .map(NodeUri::create) .map(NodeUri::asURI) .forEach(partyStore::store); return Response.ok().build(); } PartyInfoResource(
        final Discovery discovery,
        final PartyInfoParser partyInfoParser,
        final Client restClient,
        final Enclave enclave,
        final PayloadEncoder payloadEncoder,
        final boolean enableKeyValidation,
        final PartyStore partyStore);  PartyInfoResource(
        final Discovery discovery,
        final PartyInfoParser partyInfoParser,
        final Client restClient,
        final Enclave enclave,
        final boolean enableKeyValidation); @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation(value = "Request key/url of other nodes") @ApiResponses({ @ApiResponse( code = 200, message = "Empty response if node is using remote key validation, else an encoded partyinfo containing only the local node's URL", response = byte[].class), @ApiResponse(code = 500, message = "If node is using remote key validation, indicates validation failed") }) Response partyInfo(
        @ApiParam(required = true) final byte[] payload,
        @HeaderParam(Constants.API_VERSION_HEADER) final List<String> headers); @GET @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Fetch network/peer information") @ApiResponses({@ApiResponse(code = 200, message = "Peer/Network information", response = PartyInfo.class)}) Response getPartyInfo(); @POST @Path("validate") @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.TEXT_PLAIN) @ApiOperation(value = "Processes the validation request data and returns the result.") @ApiResponses({ @ApiResponse( code = 200, message = "Validation request data successfully processed and returned", response = String.class), @ApiResponse(code = 400, message = "Validation request data is not a valid UUID") }) Response validate(byte[] payloadData);  }