From 4d3c71e0a67a416a6f6322a0de3acc9deb90914d Mon Sep 17 00:00:00 2001 From: elmiomar Date: Fri, 2 Feb 2024 09:42:00 -0500 Subject: [PATCH 1/5] add endpoint for removing datasets or files from cache --- .../web/CacheManagementController.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/main/java/gov/nist/oar/distrib/web/CacheManagementController.java b/src/main/java/gov/nist/oar/distrib/web/CacheManagementController.java index d17ef602..c42593d0 100644 --- a/src/main/java/gov/nist/oar/distrib/web/CacheManagementController.java +++ b/src/main/java/gov/nist/oar/distrib/web/CacheManagementController.java @@ -11,6 +11,7 @@ */ package gov.nist.oar.distrib.web; +import gov.nist.oar.distrib.cachemgr.VolumeStatus; import gov.nist.oar.distrib.cachemgr.pdr.PDRCacheManager; import gov.nist.oar.distrib.cachemgr.CacheManagementException; import gov.nist.oar.distrib.cachemgr.CacheObject; @@ -331,6 +332,46 @@ else if (":checked".equals(selector)) return new ResponseEntity("Method not allowed on URL", HttpStatus.METHOD_NOT_ALLOWED); } + /** + * Endpoint to remove a dataset or specific files within a dataset from the cache. + * + * @param dsid the dataset identifier + * @param request used to extract the optional file path from the URL + * @return ResponseEntity with the result of the operation + */ + @DeleteMapping(value="/objects/{dsid}/**") + public ResponseEntity removeFromCache(@PathVariable("dsid") String dsid, HttpServletRequest request) { + try { + + _checkForManager(); + + // Extract the optional file path from the request URL + String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); + String prefix = "/cache/objects/" + dsid; + String filepath = path.startsWith(prefix) ? path.substring(prefix.length()) : ""; + + if (filepath.isEmpty() || filepath.equals("/")) { + // Remove the entire dataset from the cache + List files = mgr.selectDatasetObjects(dsid, VolumeStatus.VOL_FOR_UPDATE); + for (CacheObject file : files) { + mgr.uncache(file.id); // Use the uncache method directly + } + return ResponseEntity.ok("Dataset " + dsid + " removed from cache"); + } else { + // Remove specific file or directory within the dataset from the cache + List files = mgr.selectFileObjects(dsid, filepath, VolumeStatus.VOL_FOR_UPDATE); + for (CacheObject file : files) { + mgr.uncache(file.id); + } + return ResponseEntity.ok("File(s) " + filepath + " in dataset " + dsid + " removed from cache"); + } + } catch (NotOperatingException e) { + return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Cache manager is not operational"); + } catch (CacheManagementException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error processing cache removal request: " + e.getMessage()); + } + } + /** * return status information about the caching queue. The caching queue is a queue of data items * waiting to be cached From 7292e5d5fc9e5a7f77ed021fc93965f1948ea83e Mon Sep 17 00:00:00 2001 From: elmiomar Date: Thu, 7 Mar 2024 11:10:07 -0500 Subject: [PATCH 2/5] update the remove from cache logic to take into account the selector :cached --- .../web/CacheManagementController.java | 59 ++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/main/java/gov/nist/oar/distrib/web/CacheManagementController.java b/src/main/java/gov/nist/oar/distrib/web/CacheManagementController.java index c42593d0..0d21e3d1 100644 --- a/src/main/java/gov/nist/oar/distrib/web/CacheManagementController.java +++ b/src/main/java/gov/nist/oar/distrib/web/CacheManagementController.java @@ -231,14 +231,17 @@ else if (":cached".equals(selector)) } List files = mgr.selectDatasetObjects(dsid, mgr.VOL_FOR_INFO); - if (files.size() == 0) - throw new ResourceNotFoundException(dsid); if (filepath.length() > 0) files = mgr.selectFileObjects(dsid, filepath, purpose); else if (purpose != mgr.VOL_FOR_INFO) files = mgr.selectDatasetObjects(dsid, purpose); + // Ensure that a ResourceNotFoundException is thrown if the files list is empty + // after all the selection logic has been applied + if (files.size() == 0) + throw new ResourceNotFoundException(dsid); + if (selector == null && filepath.length() > 0) { // return a single JSON object; get the one that's cached List use = files.stream().filter(c -> c.cached).collect(Collectors.toList()); @@ -333,7 +336,12 @@ else if (":checked".equals(selector)) } /** - * Endpoint to remove a dataset or specific files within a dataset from the cache. + * Removes a dataset or specific files within a dataset from the cache based on the provided dataset identifier (dsid). + * This endpoint supports selective removal using the ":cached" selector in the URL path, allowing for more granular + * control over cache management. + * + * If the ":cached" selector is present, and no specific file path is provided, the entire dataset identified by the dsid + * is removed from the cache. If a specific file path is provided, only the specified file within the dataset will be removed. * * @param dsid the dataset identifier * @param request used to extract the optional file path from the URL @@ -344,30 +352,51 @@ public ResponseEntity removeFromCache(@PathVariable("dsid") String dsid, try { _checkForManager(); + log.debug("Attempting to remove files from cache for Dataset ID: {}", dsid); // Extract the optional file path from the request URL String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); String prefix = "/cache/objects/" + dsid; String filepath = path.startsWith(prefix) ? path.substring(prefix.length()) : ""; - if (filepath.isEmpty() || filepath.equals("/")) { - // Remove the entire dataset from the cache - List files = mgr.selectDatasetObjects(dsid, VolumeStatus.VOL_FOR_UPDATE); - for (CacheObject file : files) { - mgr.uncache(file.id); // Use the uncache method directly + String selector = null; + Matcher selmatch = SEL_PATH_FIELD.matcher(filepath); + if (selmatch.find()) { + selector = selmatch.group(1); + filepath = filepath.substring(0, selmatch.start()); + } + if (filepath.startsWith("/")) { + filepath = filepath.substring(1); // Remove leading slash + } + + if (":cached".equals(selector)) { + if (filepath.isEmpty() || filepath.equals("/")) { + log.debug("Removing entire dataset from cache for ID: {}", dsid); + List files = mgr.selectDatasetObjects(dsid, VolumeStatus.VOL_FOR_UPDATE); + for (CacheObject file : files) { + log.debug("Uncaching file: {}", file.id); + mgr.uncache(file.id); + } + return ResponseEntity.ok("Dataset " + dsid + " removed from cache"); + } else { + log.debug("Removing file(s) from cache for dataset ID: {} and path: {}", dsid, filepath); + List files = mgr.selectFileObjects(dsid, filepath, VolumeStatus.VOL_FOR_UPDATE); + for (CacheObject file : files) { + log.debug("Uncaching file: {}", file.id); + mgr.uncache(file.id); + } + return ResponseEntity.ok("File(s) " + filepath + " in dataset " + dsid + " removed from cache"); } - return ResponseEntity.ok("Dataset " + dsid + " removed from cache"); } else { - // Remove specific file or directory within the dataset from the cache - List files = mgr.selectFileObjects(dsid, filepath, VolumeStatus.VOL_FOR_UPDATE); - for (CacheObject file : files) { - mgr.uncache(file.id); - } - return ResponseEntity.ok("File(s) " + filepath + " in dataset " + dsid + " removed from cache"); + log.warn("Operation not allowed: URL does not contain ':cached' selector"); + return new ResponseEntity("Operation not allowed on URL without :cached selector", HttpStatus.METHOD_NOT_ALLOWED); } + } catch (NotOperatingException e) { + log.error("Cache manager is not operational", e); return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Cache manager is not operational"); } catch (CacheManagementException e) { + log.error("Error processing cache removal request", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error processing cache removal request: " + e.getMessage()); } } From 6b6ac3886c04f3031eb4513c1b3ae27e627f81c8 Mon Sep 17 00:00:00 2001 From: elmiomar Date: Thu, 7 Mar 2024 11:10:46 -0500 Subject: [PATCH 3/5] unit test for the new remove from cache endpoint --- .../web/CacheManagementControllerTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/test/java/gov/nist/oar/distrib/web/CacheManagementControllerTest.java b/src/test/java/gov/nist/oar/distrib/web/CacheManagementControllerTest.java index 9aac4d53..02e05598 100644 --- a/src/test/java/gov/nist/oar/distrib/web/CacheManagementControllerTest.java +++ b/src/test/java/gov/nist/oar/distrib/web/CacheManagementControllerTest.java @@ -404,6 +404,44 @@ public void testStartMonitor() throws ConfigurationException { assertFalse("Monitor failed to finish?", status.getBoolean("running")); } + @Test + public void testRemoveFromCache() { + HttpEntity req = new HttpEntity<>(null, headers); + + // First, ensure the dataset or file initially exists in the cache by sending a GET request + // This is a check to confirm presence before deletion + ResponseEntity resp = websvc.exchange(getBaseURL() + "/cache/objects/mds1491/:cached", + HttpMethod.GET, req, String.class); + assertEquals(HttpStatus.OK, resp.getStatusCode()); + + // Attempt to remove a specific file (trial2.json) from the cache using the DELETE method + // The file path and the ':cached' selector are included in the URL + ResponseEntity removeResp = websvc.exchange(getBaseURL() + + "/cache/objects/mds1491/trial2.json/:cached", + HttpMethod.DELETE, req, String.class); + assertEquals(HttpStatus.OK, removeResp.getStatusCode()); + + // After deletion, verify that the specific file (trial2.json) is no longer accessible + // Expect a NOT_FOUND status code to confirm successful deletion + resp = websvc.exchange(getBaseURL() + "/cache/objects/mds1491/trial2.json/:cached", + HttpMethod.GET, req, String.class); + assertEquals(HttpStatus.NOT_FOUND, resp.getStatusCode()); + + // Now, attempt to remove the entire dataset ('mds1491') from the cache + // This tests the capability to delete all files under a dataset identifier + removeResp = websvc.exchange(getBaseURL() + + "/cache/objects/mds1491/:cached", + HttpMethod.DELETE, req, String.class); + assertEquals(HttpStatus.OK, removeResp.getStatusCode()); + + // Confirm that the entire dataset ('mds1491') is no longer available in the cache + // A GET request should return a NOT_FOUND status, meaning the dataset has been successfully removed from cache + resp = websvc.exchange(getBaseURL() + "/cache/objects/mds1491/:cached", + HttpMethod.GET, req, String.class); + assertEquals(HttpStatus.NOT_FOUND, resp.getStatusCode()); + } + + private String getBaseURL() { return "http://localhost:" + port + "/od"; } From 2df7be4f54df09c2121eff77f33d2fd4be72b47b Mon Sep 17 00:00:00 2001 From: elmiomar Date: Thu, 7 Mar 2024 11:18:57 -0500 Subject: [PATCH 4/5] remove redundant rpa service interface and class --- .../rpa/DefaultRPARequestHandlerService.java | 397 ------------------ .../service/rpa/RPARequestHandlerService.java | 49 --- .../distrib/web/NISTDistribServiceConfig.java | 2 - .../oar/distrib/web/RPAServiceProvider.java | 12 - 4 files changed, 460 deletions(-) delete mode 100644 src/main/java/gov/nist/oar/distrib/service/rpa/DefaultRPARequestHandlerService.java delete mode 100644 src/main/java/gov/nist/oar/distrib/service/rpa/RPARequestHandlerService.java diff --git a/src/main/java/gov/nist/oar/distrib/service/rpa/DefaultRPARequestHandlerService.java b/src/main/java/gov/nist/oar/distrib/service/rpa/DefaultRPARequestHandlerService.java deleted file mode 100644 index 2acf61f9..00000000 --- a/src/main/java/gov/nist/oar/distrib/service/rpa/DefaultRPARequestHandlerService.java +++ /dev/null @@ -1,397 +0,0 @@ -package gov.nist.oar.distrib.service.rpa; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import gov.nist.oar.distrib.service.rpa.exceptions.FailedRecordUpdateException; -import gov.nist.oar.distrib.service.rpa.exceptions.InvalidRecaptchaException; -import gov.nist.oar.distrib.service.rpa.exceptions.InvalidRequestException; -import gov.nist.oar.distrib.service.rpa.exceptions.RecordNotFoundException; -import gov.nist.oar.distrib.service.rpa.exceptions.UnauthorizedException; -import gov.nist.oar.distrib.service.rpa.model.EmailInfo; -import gov.nist.oar.distrib.service.rpa.model.EmailInfoWrapper; -import gov.nist.oar.distrib.service.rpa.model.JWTToken; -import gov.nist.oar.distrib.service.rpa.model.RecaptchaResponse; -import gov.nist.oar.distrib.service.rpa.model.Record; -import gov.nist.oar.distrib.service.rpa.model.RecordStatus; -import gov.nist.oar.distrib.service.rpa.model.RecordWrapper; -import gov.nist.oar.distrib.service.rpa.model.UserInfoWrapper; -import gov.nist.oar.distrib.web.RPAConfiguration; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import lombok.RequiredArgsConstructor; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Date; - -/** - * Default implementation of the RPARequestHandlerService. - */ -@RequiredArgsConstructor -public class DefaultRPARequestHandlerService implements RPARequestHandlerService { - - private final static Logger LOGGER = LoggerFactory.getLogger(DefaultRPARequestHandlerService.class); - private final static String API_TEST_ENDPOINT_KEY = "api-test-endpoint"; - private final static String GET_RECORD_ENDPOINT_KEY = "get-record-endpoint"; - private final static String CREATE_RECORD_ENDPOINT_KEY = "create-record-endpoint"; - private final static String UPDATE_RECORD_ENDPOINT_KEY = "update-record-endpoint"; - private final static String SEND_EMAIL_ENDPOINT_KEY = "send-email-endpoint"; - - private RPAConfiguration rpaConfiguration = null; - private KeyRetriever keyRetriever = null; - private RestTemplate restTemplate; - - /** - * Constructs a new DefaultRPARequestHandlerService object with the given rpaConfiguration and keyRetriever. - * @param rpaConfiguration The Restricted Public Access (RPA) configuration object - * @param keyRetriever The private key retriever object - */ - public DefaultRPARequestHandlerService(RPAConfiguration rpaConfiguration, RestTemplate restTemplate) { - this.rpaConfiguration = rpaConfiguration; - this.keyRetriever = new JKSKeyRetriever(); - this.restTemplate = new RestTemplate(); - // We need to include HttpComponentsClientHttpRequestFactory because The standard JDK HTTP library - // does not support HTTP PATCH. We need to use the Apache HttpComponents or OkHttp request factory. - HttpClient httpClient = HttpClientBuilder.create().build(); - this.restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient)); - - LOGGER.debug("RPA_CONFIGURATION=" + this.rpaConfiguration.toString()); - } - - /** - * Returns the RPA configuration. This is a bean that is injected into the service. - * The RPA configuration is loaded from the config server. - * - * @return The RPA configuration - */ - public RPAConfiguration getConfig() { return this.rpaConfiguration; } - - - /** - * Get information about a specific record. - * - * This asks for an access token, then uses it to add the bearer auth http header. - * Constructs the URL, and sends a GET request to fetch information about the record. - * - * @param recordId the identifier for the record. - * - * @exception UnauthorizedException if there is an issue with the access token. - * @exception RecordNotFoundException if record is not found. - * - * @return RecordWrapper -- the requested record wrapped within a "record" envelope. - */ - @Override - public RecordWrapper getRecord(String recordId) throws RecordNotFoundException, UnauthorizedException { - String getRecordUri = getConfig().getSalesforceEndpoints().get(GET_RECORD_ENDPOINT_KEY); - JWTToken token = getToken(); - HttpHeaders headers = getHttpHeaders(token, null); - String url = UriComponentsBuilder.fromUriString(token.getInstanceUrl()) - .path(getRecordUri + "/" + recordId) - .toUriString(); - ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, - new HttpEntity<>(headers), RecordWrapper.class); - if (response.getStatusCode() == HttpStatus.NOT_FOUND) { - throw RecordNotFoundException.fromRecordId(recordId); - } - return response.getBody(); - } - - /** - * Create a new record. - * - * This asks for an access token, then uses it to add the bearer auth http header. - * Constructs the URL, and sends a POST request to create a new record. - * - * @param userInfoWrapper the information provided by the user. - * - * @exception UnauthorizedException if there is an issue with the access token. - * @exception InvalidRecaptchaException if there is an issue processing the recaptcha. - * @exception InvalidRequestException if there is an issue with the request fields. - * - * @return RecordWrapper -- the newly created record wrapped within a "record" envelope. - */ - @Override - public RecordWrapper createRecord(UserInfoWrapper userInfoWrapper) - throws InvalidRecaptchaException, InvalidRequestException, UnauthorizedException { - - String createRecordUri = getConfig().getSalesforceEndpoints().get(CREATE_RECORD_ENDPOINT_KEY); - JWTToken token = getToken(); - HttpHeaders headers = getHttpHeaders(token, MediaType.APPLICATION_JSON); - HttpEntity request; - ObjectMapper mapper = new ObjectMapper(); - - // first check if recaptcha is valid - RecaptchaResponse recaptchaResponse = verifyRecaptcha(userInfoWrapper.getRecaptcha()); - if (!recaptchaResponse.isSuccess()) { - throw new InvalidRecaptchaException("failed to verify the reCaptcha: " + recaptchaResponse.getErrorCodes()); - } - try { - // cleaning form input from any HTML - UserInfoWrapper cleanUserInfoWrapper = HTMLCleaner.clean(userInfoWrapper); - String payload = mapper.writeValueAsString(cleanUserInfoWrapper); - LOGGER.debug("PAYLOAD=" + payload); - request = new HttpEntity<>(payload, headers); - } catch (JsonProcessingException e) { - throw new InvalidRequestException("could not clean form fields: " + e.getMessage()); - } - String url = token.getInstanceUrl() + createRecordUri; - ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, request, RecordWrapper.class); - if (responseEntity.getStatusCode().equals(HttpStatus.OK)) { - onSuccessfulRecordCreation(responseEntity.getBody().getRecord()); - } else { - onFailedRecordCreation(responseEntity.getStatusCode()); - } - return responseEntity.getBody(); - } - - /** - * Function to verify the recaptcha token. - */ - private RecaptchaResponse verifyRecaptcha(String token) { - String url = UriComponentsBuilder.fromUriString("https://www.google.com") - .path("/recaptcha/api/siteverify") - .queryParam("secret", getConfig().getRecaptchaSecret()) - .queryParam("response", token) - .toUriString(); - - LOGGER.debug("RECAPTCHA_URL=" + url); - HttpHeaders headers = new HttpHeaders(); - return restTemplate.postForObject(url, new HttpEntity<>(null, headers), RecaptchaResponse.class); - } - - - /** - * On successful creation of a record, send a confirmation email to the user, and another email to the approver. - * Check if sending of emails was successful. - */ - private void onSuccessfulRecordCreation(Record record) throws UnauthorizedException { - LOGGER.debug("Record created successfully! Now sending emails..."); - if (sendConfirmationEmailToEndUser(record).equals(HttpStatus.OK)) { - LOGGER.debug("Confirmation email sent to end user successfully!"); - } - if (sendApprovalEmailToSME(record).equals(HttpStatus.OK)) { - LOGGER.debug("Request approval email sent to subject matter expert successfully!"); - } - } - - /** - * On failed creation of a record - * TODO: add logic when creation fails - */ - private void onFailedRecordCreation(HttpStatus statusCode) { - // handle failed record creation - } - - - /** - * Update the status of a specific record. - * - * This asks for an access token, then uses it to add the bearer auth http header. - * Constructs the URL, and sends a PATCH request to update a record. - * - * @param recordId the identifier for the record. - * @param status the new status. - * - * @exception UnauthorizedException if there is an issue with the access token. - * @exception RecordNotFoundException if record is not found. - * @exception FailedRecordUpdateException if the record update failed. - * - * @return RecordStatus -- the updated status of the record. - */ - @Override - public RecordStatus updateRecord(String recordId, String status) - throws RecordNotFoundException, UnauthorizedException, FailedRecordUpdateException { - - String updateRecordUri = getConfig().getSalesforceEndpoints().get(UPDATE_RECORD_ENDPOINT_KEY); - JSONObject updateBody = new JSONObject(); - updateBody.put("Approval_Status__c", status); - JWTToken token = getToken(); - HttpHeaders headers = getHttpHeaders(token, MediaType.APPLICATION_JSON); - String patch = updateBody.toString(); - LOGGER.debug("PATCH_DATA=" + patch); - HttpEntity request = new HttpEntity<>(patch, headers); - String url = UriComponentsBuilder.fromUriString(token.getInstanceUrl()) - .path(updateRecordUri + "/" + recordId) - .toUriString(); - LOGGER.debug("UPDATE_URL=" + url); - ResponseEntity responseEntity = null; - try { - responseEntity= restTemplate.exchange( - url, HttpMethod.PATCH, request, RecordStatus.class - ); - } catch (HttpStatusCodeException e) { - LOGGER.debug("failed to update record: " + e.getResponseBodyAsString()); - throw FailedRecordUpdateException.forID(recordId); - } - - // check if status is approved and trigger the caching process - LOGGER.debug("APPROVAL_STATUS=" + responseEntity.getBody().getApprovalStatus()); - if (responseEntity.getBody().getApprovalStatus().toLowerCase().contains("approved")) { - onEndUserApproved(recordId); - } - // check if status is declined and send email notification to user - if (responseEntity.getBody().getApprovalStatus().toLowerCase().contains("declined")) { - // Don't do anything if User is declined - // onEndUserDeclined(recordId); - } - return responseEntity.getBody(); - } - - private HttpStatus onEndUserApproved(String recordId) throws RecordNotFoundException, UnauthorizedException { - LOGGER.info("User was approved by SME. Starting caching..."); - Record record = getRecord(recordId).getRecord(); - String datasetId = record.getUserInfo().getSubject(); - String randomId = startCaching(datasetId); - String downloadUrl = UriComponentsBuilder.fromUriString(getConfig().getDatacartUrl()) - .queryParam("id", randomId) - .toUriString(); - LOGGER.info("Dataset was cached successfully. Sending email to user..."); - return sendDownloadEmailToEndUser(record, downloadUrl); - } - - private HttpStatus onEndUserDeclined(String recordId) throws RecordNotFoundException, UnauthorizedException { - LOGGER.info("User was declined by SME. Sending declined email to user..."); - Record record = getRecord(recordId).getRecord(); - return sendDeclinedEmailToEndUser(record); - } - - // Function to call the caching the service to start the caching process - // This function returns a temporary URL to the datacart that contains the cached dataset - private String startCaching(String datasetId) { - String url = getConfig().getPdrCachingUrl() + "/cache/" + datasetId; - HttpEntity request = new HttpEntity<>(null, new HttpHeaders()); - ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.PUT, request, String.class); - return responseEntity.getBody(); - } - - private HttpStatus sendApprovalEmailToSME(Record record) throws UnauthorizedException { - EmailInfo emailInfo = EmailHelper.getSMEApprovalEmailInfo(record, getConfig()); - LOGGER.debug("EMAIL_INFO=" + emailInfo); - ResponseEntity responseEntity = this.sendEmail(emailInfo); - return responseEntity.getStatusCode(); - } - - private HttpStatus sendConfirmationEmailToEndUser(Record record) throws UnauthorizedException { - EmailInfo emailInfo = EmailHelper.getEndUserConfirmationEmailInfo(record, getConfig()); - LOGGER.debug("EMAIL_INFO=" + emailInfo); - ResponseEntity responseEntity = this.sendEmail(emailInfo); - return responseEntity.getStatusCode(); - } - - private HttpStatus sendDownloadEmailToEndUser(Record record, String downloadUrl) throws UnauthorizedException { - EmailInfo emailInfo = EmailHelper.getEndUserDownloadEmailInfo(record, getConfig(), downloadUrl); - LOGGER.info("EMAIL_INFO=" + emailInfo); - ResponseEntity responseEntity = this.sendEmail(emailInfo); - return responseEntity.getStatusCode(); - } - - private HttpStatus sendDeclinedEmailToEndUser(Record record) throws UnauthorizedException { - EmailInfo emailInfo = EmailHelper.getEndUserDeclinedEmailInfo(record, getConfig()); - LOGGER.debug("EMAIL_INFO=" + emailInfo); - ResponseEntity responseEntity = this.sendEmail(emailInfo); - return responseEntity.getStatusCode(); - } - - - private ResponseEntity sendEmail(EmailInfo emailInfo) throws UnauthorizedException { - String sendEmailUri = getConfig().getSalesforceEndpoints().get(SEND_EMAIL_ENDPOINT_KEY); - JWTToken token = getToken(); - HttpHeaders headers = getHttpHeaders(token, MediaType.APPLICATION_JSON); - ObjectMapper mapper = new ObjectMapper(); - HttpEntity request; - try { - request = new HttpEntity<>(mapper.writeValueAsString(emailInfo), headers); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - String url = token.getInstanceUrl() + sendEmailUri; - return restTemplate.exchange(url, HttpMethod.POST, request, EmailInfoWrapper.class); - } - - - /** - * Test connection to Salesforce service to make sure the Connected App is working. - */ - public String testSalesforceAPIConnection() throws UnauthorizedException { - String testUri = getConfig().getSalesforceEndpoints().get(API_TEST_ENDPOINT_KEY); - JWTToken token = getToken(); - HttpHeaders headers = getHttpHeaders(token, null); - ResponseEntity response = restTemplate.exchange(token.getInstanceUrl() + testUri, - HttpMethod.GET, new HttpEntity<>(headers), String.class); - return response.getBody(); - } - - private HttpHeaders getHttpHeaders(JWTToken token, MediaType mediaType) { - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(token.getAccessToken()); - if (mediaType != null) - headers.setContentType(mediaType); - return headers; - } - - // JWT methods - - // Retrieve the JWT Access Token - private JWTToken getToken() throws UnauthorizedException { - return sendTokenRequest(createAssertion()); - } - - // Create the jwt assertion. - private String createAssertion() { - LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(getConfig().getSalesforceJwt().getExpirationInMinutes()); - return Jwts.builder() - .setIssuer(getConfig().getSalesforceJwt().getClientId()) - .setAudience(getConfig().getSalesforceJwt().getAudience()) - .setSubject(getConfig().getSalesforceJwt().getSubject()) - .setExpiration(Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())) - .signWith(SignatureAlgorithm.RS256, keyRetriever.getKey(getConfig())) - .compact(); - } - - /** - * Send token request as per rfc7523. - * - * @param assertion - the assertion token containing a JWT token - * - * @return JWTToken - the access token - */ - private JWTToken sendTokenRequest(String assertion) throws UnauthorizedException { - String url = UriComponentsBuilder.fromUriString(getConfig().getSalesforceInstanceUrl()) - .path("/services/oauth2/token") - .queryParam("grant_type", getConfig().getSalesforceJwt().getGrantType()) - .queryParam("assertion", assertion) - .toUriString(); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - ResponseEntity response; - try { - response = restTemplate.exchange( - url, - HttpMethod.POST, - new HttpEntity<>(null, headers), - JWTToken.class - ); - } catch (HttpStatusCodeException e) { - LOGGER.debug("access token request is invalid: " + e.getResponseBodyAsString()); - throw new UnauthorizedException("access token request is invalid: " + e.getResponseBodyAsString()); - } - return response.getBody(); - } - - -} diff --git a/src/main/java/gov/nist/oar/distrib/service/rpa/RPARequestHandlerService.java b/src/main/java/gov/nist/oar/distrib/service/rpa/RPARequestHandlerService.java deleted file mode 100644 index 0bc5660c..00000000 --- a/src/main/java/gov/nist/oar/distrib/service/rpa/RPARequestHandlerService.java +++ /dev/null @@ -1,49 +0,0 @@ -package gov.nist.oar.distrib.service.rpa; - -import gov.nist.oar.distrib.service.rpa.exceptions.FailedRecordUpdateException; -import gov.nist.oar.distrib.service.rpa.exceptions.InvalidRecaptchaException; -import gov.nist.oar.distrib.service.rpa.exceptions.InvalidRequestException; -import gov.nist.oar.distrib.service.rpa.exceptions.RecaptchaClientException; -import gov.nist.oar.distrib.service.rpa.exceptions.RecaptchaVerificationFailedException; -import gov.nist.oar.distrib.service.rpa.exceptions.RecordNotFoundException; -import gov.nist.oar.distrib.service.rpa.exceptions.UnauthorizedException; -import gov.nist.oar.distrib.service.rpa.model.RecordStatus; -import gov.nist.oar.distrib.service.rpa.model.RecordWrapper; -import gov.nist.oar.distrib.service.rpa.model.UserInfoWrapper; - - -/** - * Service interface for handling RPA request submissions by end users. - * When an end user submits a new request, a new record will be created. - */ -public interface RPARequestHandlerService { - - /** - * Get information about a specific record. - * - * @param recordId the identifier for the record. - * @return RecordWrapper -- the requested record wrapped within a "record" envelope. - */ - RecordWrapper getRecord(String recordId) throws RecordNotFoundException, UnauthorizedException; - - /** - * Create a new record. - * - * @param userInfoWrapper the information provided by the user. - * @return RecordWrapper -- the newly created record wrapped within a "record" envelope. - */ - RecordWrapper createRecord(UserInfoWrapper userInfoWrapper) throws InvalidRecaptchaException, - InvalidRequestException, UnauthorizedException, RecaptchaVerificationFailedException, - RecaptchaClientException; - - /** - * Update the status of a specific record. - * - * @param recordId the identifier for the record. - * @param status the new status. - * @return RecordStatus -- the updated status of the record. - */ - RecordStatus updateRecord(String recordId, String status) throws RecordNotFoundException, UnauthorizedException, - FailedRecordUpdateException; - -} diff --git a/src/main/java/gov/nist/oar/distrib/web/NISTDistribServiceConfig.java b/src/main/java/gov/nist/oar/distrib/web/NISTDistribServiceConfig.java index 2ae27f21..f307ab5a 100644 --- a/src/main/java/gov/nist/oar/distrib/web/NISTDistribServiceConfig.java +++ b/src/main/java/gov/nist/oar/distrib/web/NISTDistribServiceConfig.java @@ -18,10 +18,8 @@ import gov.nist.oar.distrib.service.FileDownloadService; import gov.nist.oar.distrib.service.PreservationBagService; import gov.nist.oar.distrib.service.RPACachingService; -import gov.nist.oar.distrib.service.rpa.DefaultRPARequestHandlerService; import gov.nist.oar.distrib.service.rpa.JKSKeyRetriever; import gov.nist.oar.distrib.service.rpa.KeyRetriever; -import gov.nist.oar.distrib.service.rpa.RPARequestHandlerService; import gov.nist.oar.distrib.storage.AWSS3LongTermStorage; import gov.nist.oar.distrib.storage.FilesystemLongTermStorage; import gov.nist.oar.distrib.cachemgr.CacheManagementException; diff --git a/src/main/java/gov/nist/oar/distrib/web/RPAServiceProvider.java b/src/main/java/gov/nist/oar/distrib/web/RPAServiceProvider.java index 6466351e..e7abd7f1 100644 --- a/src/main/java/gov/nist/oar/distrib/web/RPAServiceProvider.java +++ b/src/main/java/gov/nist/oar/distrib/web/RPAServiceProvider.java @@ -1,12 +1,8 @@ package gov.nist.oar.distrib.web; import gov.nist.oar.distrib.service.RPACachingService; -import gov.nist.oar.distrib.service.rpa.DefaultRPARequestHandlerService; import gov.nist.oar.distrib.service.rpa.HttpURLConnectionRPARequestHandlerService; import gov.nist.oar.distrib.service.rpa.IRPARequestHandler; -import gov.nist.oar.distrib.service.rpa.RPARequestHandlerService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.client.RestTemplate; public class RPAServiceProvider { RPAConfiguration rpaConfiguration; @@ -15,14 +11,6 @@ public RPAServiceProvider(RPAConfiguration rpaConfiguration) { this.rpaConfiguration = rpaConfiguration; } - public RPARequestHandlerService getRPARequestHandlerService(RestTemplate restTemplate) { - return this.getDefaultRPARequestHandlerService(restTemplate); - } - - private DefaultRPARequestHandlerService getDefaultRPARequestHandlerService(RestTemplate restTemplate) { - return new DefaultRPARequestHandlerService(this.rpaConfiguration, restTemplate); - } - public IRPARequestHandler getIRPARequestHandler(RPACachingService rpaCachingService) { return this.getHttpURLConnectionRPARequestHandler(rpaCachingService); } From 383cc7c926f8c406b41e1e92b30f04815143cc2e Mon Sep 17 00:00:00 2001 From: elmiomar Date: Thu, 7 Mar 2024 11:33:50 -0500 Subject: [PATCH 5/5] change RPARequestHandler interface name and update javadoc --- ...HttpURLConnectionRPARequestHandlerService.java | 15 +++++++-------- ...RequestHandler.java => RPARequestHandler.java} | 6 ++++-- .../distrib/web/RPARequestHandlerController.java | 6 +++--- .../nist/oar/distrib/web/RPAServiceProvider.java | 6 +++--- .../web/RPARequestHandlerControllerTest.java | 6 +++--- 5 files changed, 20 insertions(+), 19 deletions(-) rename src/main/java/gov/nist/oar/distrib/service/rpa/{IRPARequestHandler.java => RPARequestHandler.java} (87%) diff --git a/src/main/java/gov/nist/oar/distrib/service/rpa/HttpURLConnectionRPARequestHandlerService.java b/src/main/java/gov/nist/oar/distrib/service/rpa/HttpURLConnectionRPARequestHandlerService.java index 1ba31865..a3e3c53c 100644 --- a/src/main/java/gov/nist/oar/distrib/service/rpa/HttpURLConnectionRPARequestHandlerService.java +++ b/src/main/java/gov/nist/oar/distrib/service/rpa/HttpURLConnectionRPARequestHandlerService.java @@ -3,15 +3,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import gov.nist.oar.distrib.service.RPACachingService; -import gov.nist.oar.distrib.service.rpa.exceptions.InvalidRecaptchaException; import gov.nist.oar.distrib.service.rpa.exceptions.InvalidRequestException; -import gov.nist.oar.distrib.service.rpa.exceptions.RecaptchaClientException; -import gov.nist.oar.distrib.service.rpa.exceptions.RecaptchaServerException; -import gov.nist.oar.distrib.service.rpa.exceptions.RecaptchaVerificationFailedException; import gov.nist.oar.distrib.service.rpa.exceptions.RecordNotFoundException; import gov.nist.oar.distrib.service.rpa.exceptions.RequestProcessingException; import gov.nist.oar.distrib.service.rpa.model.JWTToken; -import gov.nist.oar.distrib.service.rpa.model.RecaptchaResponse; import gov.nist.oar.distrib.service.rpa.model.Record; import gov.nist.oar.distrib.service.rpa.model.RecordStatus; import gov.nist.oar.distrib.service.rpa.model.RecordWrapper; @@ -45,10 +40,14 @@ /** - * An implementation of the RPARequestHandlerService that uses HttpURLConnection to send HTTP requests and - * receives responses from the Salesforce service. + * An implementation of the {@link RPARequestHandler} interface that uses HttpURLConnection + * to send HTTP requests and receive responses from the Salesforce service. This class serves + * as a bridge between the application and Salesforce, acting as the data source for managing + * records. Through this service, records can be created, retrieved, and updated directly in + * Salesforce, using the platform's API for record management. + * */ -public class HttpURLConnectionRPARequestHandlerService implements IRPARequestHandler { +public class HttpURLConnectionRPARequestHandlerService implements RPARequestHandler { private final static Logger LOGGER = LoggerFactory.getLogger(HttpURLConnectionRPARequestHandlerService.class); diff --git a/src/main/java/gov/nist/oar/distrib/service/rpa/IRPARequestHandler.java b/src/main/java/gov/nist/oar/distrib/service/rpa/RPARequestHandler.java similarity index 87% rename from src/main/java/gov/nist/oar/distrib/service/rpa/IRPARequestHandler.java rename to src/main/java/gov/nist/oar/distrib/service/rpa/RPARequestHandler.java index 7029b560..ac647073 100644 --- a/src/main/java/gov/nist/oar/distrib/service/rpa/IRPARequestHandler.java +++ b/src/main/java/gov/nist/oar/distrib/service/rpa/RPARequestHandler.java @@ -9,9 +9,11 @@ import gov.nist.oar.distrib.service.rpa.model.UserInfoWrapper; /** - * An interface for handling requests to manage records. + * An interface for handling requests to manage records. This includes operations + * such as creating, retrieving, and updating records. Implementations of this interface + * are responsible for the interaction with a data source to perform these operations. */ -public interface IRPARequestHandler { +public interface RPARequestHandler { /** * Updates the status of a record by ID. diff --git a/src/main/java/gov/nist/oar/distrib/web/RPARequestHandlerController.java b/src/main/java/gov/nist/oar/distrib/web/RPARequestHandlerController.java index 44bd6022..7e3e5f57 100644 --- a/src/main/java/gov/nist/oar/distrib/web/RPARequestHandlerController.java +++ b/src/main/java/gov/nist/oar/distrib/web/RPARequestHandlerController.java @@ -1,7 +1,7 @@ package gov.nist.oar.distrib.web; import gov.nist.oar.distrib.service.RPACachingService; -import gov.nist.oar.distrib.service.rpa.IRPARequestHandler; +import gov.nist.oar.distrib.service.rpa.RPARequestHandler; import gov.nist.oar.distrib.service.rpa.exceptions.InvalidRequestException; import gov.nist.oar.distrib.service.rpa.exceptions.RecaptchaClientException; import gov.nist.oar.distrib.service.rpa.exceptions.RecaptchaServerException; @@ -62,7 +62,7 @@ public class RPARequestHandlerController { /** * The primary service for handling RPA requests. */ - IRPARequestHandler service = null; + RPARequestHandler service = null; /** * The sanitizer for incoming requests. @@ -114,7 +114,7 @@ public RPARequestHandlerController(RPAServiceProvider rpaServiceProvider, RPACachingService cachingService) { if (cachingService != null && rpaServiceProvider != null) - this.service = rpaServiceProvider.getIRPARequestHandler(cachingService); + this.service = rpaServiceProvider.getRPARequestHandler(cachingService); this.requestSanitizer = new RequestSanitizer(); this.configuration = rpaServiceProvider.getRpaConfiguration(); this.jwtTokenValidator = new JwtTokenValidator(this.configuration); diff --git a/src/main/java/gov/nist/oar/distrib/web/RPAServiceProvider.java b/src/main/java/gov/nist/oar/distrib/web/RPAServiceProvider.java index e7abd7f1..fb3300b4 100644 --- a/src/main/java/gov/nist/oar/distrib/web/RPAServiceProvider.java +++ b/src/main/java/gov/nist/oar/distrib/web/RPAServiceProvider.java @@ -2,7 +2,7 @@ import gov.nist.oar.distrib.service.RPACachingService; import gov.nist.oar.distrib.service.rpa.HttpURLConnectionRPARequestHandlerService; -import gov.nist.oar.distrib.service.rpa.IRPARequestHandler; +import gov.nist.oar.distrib.service.rpa.RPARequestHandler; public class RPAServiceProvider { RPAConfiguration rpaConfiguration; @@ -11,11 +11,11 @@ public RPAServiceProvider(RPAConfiguration rpaConfiguration) { this.rpaConfiguration = rpaConfiguration; } - public IRPARequestHandler getIRPARequestHandler(RPACachingService rpaCachingService) { + public RPARequestHandler getRPARequestHandler(RPACachingService rpaCachingService) { return this.getHttpURLConnectionRPARequestHandler(rpaCachingService); } - private IRPARequestHandler getHttpURLConnectionRPARequestHandler(RPACachingService rpaCachingService) { + private RPARequestHandler getHttpURLConnectionRPARequestHandler(RPACachingService rpaCachingService) { return new HttpURLConnectionRPARequestHandlerService(this.rpaConfiguration, rpaCachingService); } diff --git a/src/test/java/gov/nist/oar/distrib/web/RPARequestHandlerControllerTest.java b/src/test/java/gov/nist/oar/distrib/web/RPARequestHandlerControllerTest.java index 5b5a14e5..5e8e1f6f 100644 --- a/src/test/java/gov/nist/oar/distrib/web/RPARequestHandlerControllerTest.java +++ b/src/test/java/gov/nist/oar/distrib/web/RPARequestHandlerControllerTest.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gov.nist.oar.distrib.service.RPACachingService; -import gov.nist.oar.distrib.service.rpa.IRPARequestHandler; +import gov.nist.oar.distrib.service.rpa.RPARequestHandler; import gov.nist.oar.distrib.service.rpa.RecaptchaHelper; import gov.nist.oar.distrib.service.rpa.exceptions.InvalidRequestException; import gov.nist.oar.distrib.service.rpa.exceptions.RecaptchaVerificationFailedException; @@ -51,7 +51,7 @@ @RunWith(MockitoJUnitRunner.class) public class RPARequestHandlerControllerTest { @Mock - private IRPARequestHandler service; + private RPARequestHandler service; @Mock RPAServiceProvider mockRPAServiceProvider; @@ -79,7 +79,7 @@ public class RPARequestHandlerControllerTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - when(mockRPAServiceProvider.getIRPARequestHandler(mockRPACachingService)).thenReturn(service); + when(mockRPAServiceProvider.getRPARequestHandler(mockRPACachingService)).thenReturn(service); // create a test instance of the RPARequestHandlerController class controller = new RPARequestHandlerController(mockRPAServiceProvider, mockRPACachingService); controller.setRequestSanitizer(mockRequestSanitizer);