Skip to content

Commit

Permalink
O3-310: allow Put operations on an application-writable config.json file
Browse files Browse the repository at this point in the history
  • Loading branch information
jnsereko committed Jan 3, 2024
1 parent 2c89d75 commit 04da905
Showing 1 changed file with 155 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.webservices.rest.web.v1_0.controller.openmrs2_4;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.openmrs.User;
import org.openmrs.api.AdministrationService;
import org.openmrs.api.context.Context;
import org.openmrs.api.context.UsernamePasswordCredentials;
import org.openmrs.module.webservices.rest.web.RestConstants;
import org.openmrs.module.webservices.rest.web.v1_0.controller.BaseRestController;
import org.openmrs.util.OpenmrsUtil;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.openmrs.module.ModuleException;

@Controller
@RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/frontend/config.json")
public class FrontendJsonConfigController2_4 extends BaseRestController {

public static final String DEFAULT_FRONTEND_DIRECTORY = "frontend";
public static final String GP_LOCAL_DIRECTORY = "spa.local.directory";
private static final String JSON_CONFIG_FILE_NAME = "config.json";

private Log log = LogFactory.getLog(this.getClass());

@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void saveFrontendConfigFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
String basicAuth = request.getHeader("Authorization");
boolean isAuthenticated = Context.isAuthenticated();
if (!isAuthenticated && basicAuth == null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorisation Provided");
return;
}
if (!isAuthenticated && isValidAuthFormat(response, basicAuth)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid credentials provided");
return;
}
User user = Context.getAuthenticatedUser();
if (user == null || !user.isSuperUser()) {
log.error("Authorization error while creating a config.json file");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
saveJsonConfigFile(request, response);
}

private void saveJsonConfigFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
File jsonConfigFile = getJsonConfigFile();
try {
BufferedReader reader = request.getReader();
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String requestBody = stringBuilder.toString();

new ObjectMapper().readTree(requestBody); // verify that is in a valid JSON format

InputStream inputStream = new ByteArrayInputStream(requestBody.getBytes(StandardCharsets.UTF_8));
OutputStream outStream = Files.newOutputStream(jsonConfigFile.toPath());
OpenmrsUtil.copyFile(inputStream, outStream);

if (jsonConfigFile.exists()) {
log.debug("file: '{" + jsonConfigFile.getAbsolutePath() + "}' written successfully");
response.setStatus(HttpServletResponse.SC_OK);
}
} catch (JsonProcessingException e) {
log.error("Invalid JSON format", e);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
}

private boolean isValidAuthFormat(HttpServletResponse response, String basicAuth) {
if (basicAuth.startsWith("Basic")) {
try {
// remove the leading "Basic "
basicAuth = basicAuth.substring(6);
if (StringUtils.isBlank(basicAuth)) {
return true;
}

String decoded = new String(Base64.getDecoder().decode(basicAuth), StandardCharsets.UTF_8);
if (StringUtils.isBlank(decoded) || !decoded.contains(":")) {
return true;
}

String[] userAndPass = decoded.split(":");
Context.authenticate(new UsernamePasswordCredentials(userAndPass[0], userAndPass[1]));
log.debug("authenticated [{" + userAndPass[0] + "}]");
} catch (Exception ex) {
// This filter never stops execution. If the user failed to
// authenticate, that will be caught later.
log.debug("authentication exception ", ex);
}
}
return false;
}

private File getJsonConfigFile() {
File folder = getSpaStaticFilesDir();
if (!folder.isDirectory()) {
log.warn("SPA frontend repository is not a directory hence creating it at: " + folder.getAbsolutePath());
if (!folder.mkdirs()) {
throw new ModuleException("Failed to create the SPA frontend repository folder at: " + folder.getAbsolutePath());
}
}
return new File(folder.getAbsolutePath(), JSON_CONFIG_FILE_NAME);
}

private File getSpaStaticFilesDir() {
AdministrationService as = Context.getAdministrationService();
String folderName = as.getGlobalProperty(GP_LOCAL_DIRECTORY, DEFAULT_FRONTEND_DIRECTORY);

// try to load the repository folder straight away.
File folder = new File(folderName);

// if the property wasn't a full path already, assume it was intended to be a
// folder in the
// application directory
if (!folder.exists()) {
folder = new File(OpenmrsUtil.getApplicationDataDirectory(), folderName);
}
return folder;
}

}

0 comments on commit 04da905

Please sign in to comment.