This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
public class CheckResource implements RESTResource {

/**
* Checks some files and folders for existance and access rights.
* Checks some files and folders for existence and access rights.
*
* @return the check result that contains a bitfield with check results for each entity
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
Expand All @@ -32,6 +33,7 @@
import javax.ws.rs.core.Response.Status;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.auth.Role;
import org.openhab.core.io.rest.RESTConstants;
import org.openhab.core.io.rest.RESTResource;
import org.openhab.ui.cometvisu.internal.Config;
Expand All @@ -55,6 +57,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;

/**
Expand All @@ -69,6 +72,8 @@
@JaxrsName(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_CONFIG_ALIAS)
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
@JSONRequired
@RolesAllowed({ Role.ADMIN })
@SecurityRequirement(name = "oauth2", scopes = { "admin" })
@Path(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_CONFIG_ALIAS)
@Tag(name = Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_CONFIG_ALIAS)
@NonNullByDefault
Expand Down Expand Up @@ -263,18 +268,7 @@ public static HiddenConfig loadHiddenConfig() {
java.nio.file.Path hiddenConfigPath = ManagerSettings.getInstance().getConfigPath().resolve("hidden.php");
if (hiddenConfigPath.toFile().exists()) {
List<String> content = Files.readAllLines(hiddenConfigPath);
boolean isPhpVersion = true;
for (int i = content.size() - 1; i >= 0; i++) {
if (content.get(i).contains("json_decode")) {
isPhpVersion = false;
break;
}
}
if (isPhpVersion) {
return loadPhpConfig(config, content);
} else {
return loadJson(String.join("\n", content));
}
return loadJson(String.join("\n", content));
}
} catch (IOException e) {
}
Expand All @@ -289,41 +283,15 @@ private static HiddenConfig loadJson(String content) {
return Objects.requireNonNull(gson.fromJson(rawContent, HiddenConfig.class));
}

private static HiddenConfig loadPhpConfig(HiddenConfig config, List<String> content) {
boolean inHidden = false;

for (final String line : content) {
if (!inHidden) {
if ("$hidden = array(".equalsIgnoreCase(line)) {
inHidden = true;
}
} else if (");".equalsIgnoreCase(line)) {
break;
} else {
Matcher m = sectionPattern.matcher(line);
if (m.find()) {
boolean commented = m.group(1) != null;
if (!commented) {
String options = m.group(3);
Matcher om = optionPattern.matcher(options);
ConfigSection section = new ConfigSection();
while (om.find()) {
section.put(om.group(1), om.group(2));
}
config.put(m.group(2), section);
}
}
}
}
return config;
}

private void writeHiddenConfig(HiddenConfig hidden) throws IOException {
java.nio.file.Path hiddenConfigPath = ManagerSettings.getInstance().getConfigPath().resolve("hidden.php");
Gson gson = new GsonBuilder().setPrettyPrinting().create();
StringBuilder content = new StringBuilder().append("<?php\n")
.append("// File for configurations that shouldn't be shared with the user\n").append("$data = '")
.append(gson.toJson(hidden)).append("';\n").append("$hidden = json_decode($data, true);\n");
.append(gson.toJson(hidden)).append("';\n").append("try {\n")
.append(" $hidden = json_decode($data, true, 512, JSON_THROW_ON_ERROR);\n")
.append("} catch (JsonException $e) {\n")
.append(" $hidden = [\"error\" => $e->getMessage(), \"data\" => $data];\n").append("}\n");
Files.writeString(hiddenConfigPath, content);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
Expand All @@ -36,6 +37,7 @@
import javax.ws.rs.core.Response.Status;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.auth.Role;
import org.openhab.core.io.rest.RESTConstants;
import org.openhab.core.io.rest.RESTResource;
import org.openhab.ui.cometvisu.internal.Config;
Expand Down Expand Up @@ -79,6 +81,7 @@ public class FsResource implements RESTResource {
private final Logger logger = LoggerFactory.getLogger(FsResource.class);

@POST
@RolesAllowed({ Role.USER, Role.ADMIN })
@Consumes("text/*")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Create a text file", responses = { @ApiResponse(responseCode = "200", description = "OK"),
Expand Down Expand Up @@ -153,6 +156,7 @@ public Response createBinary(@Context HttpServletRequest request,

@DELETE
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({ Role.USER, Role.ADMIN })
@Operation(summary = "Deletes a file/folder", responses = { @ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "403", description = "not allowed"),
@ApiResponse(responseCode = "404", description = "File/Folder not found"),
Expand Down Expand Up @@ -242,33 +246,37 @@ public Response read(
}

@PUT
@RolesAllowed({ Role.USER, Role.ADMIN })
@Produces(MediaType.APPLICATION_JSON)
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_XML })
@Operation(summary = "Update an existing file", responses = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "403", description = "not allowed"),
@ApiResponse(responseCode = "403", description = "forbidden"),
@ApiResponse(responseCode = "404", description = "File does not exist") })
public Response update(
@Parameter(description = "Relative path inside the config folder", required = true) @QueryParam("path") String path,
@Parameter(description = "file content") String body,
@Parameter(description = "CRC32 hash value of the file content", content = @Content(schema = @Schema(implementation = String.class, defaultValue = "ignore"))) @DefaultValue("ignore") @QueryParam("hash") String hash) {
File target = new File(
ManagerSettings.getInstance().getConfigFolder().getAbsolutePath() + File.separator + path);
if (target.exists()) {
if (target.canWrite()) {
try {
FsUtil.getInstance().saveFile(target, body, hash);
return Response.ok().build();
} catch (FileOperationException e) {
return FsUtil.createErrorResponse(e);
} catch (Exception e) {
try {
MountedFile target = new MountedFile(path);
if (target.exists()) {
if (target.canWrite()) {
try {
FsUtil.getInstance().saveFile(target.toFile(), body, hash);
return Response.ok().build();
} catch (FileOperationException e) {
return FsUtil.createErrorResponse(e);
} catch (Exception e) {
return FsUtil.createErrorResponse(Status.FORBIDDEN, "forbidden");
}
} else {
return FsUtil.createErrorResponse(Status.FORBIDDEN, "forbidden");
}
} else {
return FsUtil.createErrorResponse(Status.FORBIDDEN, "forbidden");
return FsUtil.createErrorResponse(Status.NOT_FOUND, "not found");
}
} else {
return FsUtil.createErrorResponse(Status.NOT_FOUND, "not found");
} catch (FileOperationException e) {
return FsUtil.createErrorResponse(e);
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.io.IOException;
import java.nio.file.Files;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
Expand All @@ -24,6 +25,7 @@
import javax.ws.rs.core.Response.Status;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.auth.Role;
import org.openhab.core.io.rest.RESTConstants;
import org.openhab.core.io.rest.RESTResource;
import org.openhab.ui.cometvisu.internal.Config;
Expand Down Expand Up @@ -56,6 +58,7 @@
@JaxrsName(Config.COMETVISU_BACKEND_ALIAS + "/fs/move")
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
@JSONRequired
@RolesAllowed({ Role.USER, Role.ADMIN })
@Path(Config.COMETVISU_BACKEND_ALIAS + "/fs/move")
@Tag(name = Config.COMETVISU_BACKEND_ALIAS + "/fs/move")
@NonNullByDefault
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;
import java.util.Base64;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
Expand Down Expand Up @@ -78,17 +81,19 @@ public class ProxyResource implements RESTResource {
@Produces({ MediaType.APPLICATION_JSON, MediaType.MEDIA_TYPE_WILDCARD })
@Operation(summary = "proxy a request", responses = { @ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "400", description = "Bad request"),
@ApiResponse(responseCode = "403", description = "Forbidden"),
@ApiResponse(responseCode = "404", description = "Not found"),
@ApiResponse(responseCode = "406", description = "Not Acceptable"),
@ApiResponse(responseCode = "500", description = "Internal server error") })
public Response proxy(
@Parameter(description = "URL this request should be sent to", content = @Content(schema = @Schema(implementation = String.class, defaultValue = ""))) @QueryParam("url") @Nullable String url,
@Parameter(description = "optional authorization token", content = @Content(schema = @Schema(implementation = String.class, defaultValue = ""))) @QueryParam("auth-type") @Nullable String authType,
@Parameter(description = "use information from hidden config section", content = @Content(schema = @Schema(implementation = String.class, defaultValue = ""))) @QueryParam("config-section") @Nullable String configSection) {
ConfigSection sec = null;
String queryUrl = url != null ? url : "";
HiddenConfig config = ConfigResource.loadHiddenConfig();
if (configSection != null && !configSection.isBlank()) {
// read URI and further information
HiddenConfig config = ConfigResource.loadHiddenConfig();
sec = config.get(configSection);
if (sec != null) {
String configUrl = sec.get("uri");
Expand All @@ -101,6 +106,29 @@ public Response proxy(
}
} else if (url == null || url.isBlank()) {
return Response.status(Status.BAD_REQUEST).build();
} else {
ConfigSection whiteList = config.get("proxy.whitelist");
boolean allowed = false;
if (whiteList != null) {
for (Map.Entry<String, String> entry : whiteList.entrySet()) {
String value = entry.getValue();
if (value.startsWith("/") && value.endsWith("/")) {
Pattern pattern = Pattern.compile(value.substring(1, value.length() - 1),
Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(queryUrl);
if (matcher.find()) {
allowed = true;
break;
}
} else if (value.equalsIgnoreCase(queryUrl)) {
allowed = true;
break;
}
}
}
if (!allowed) {
return Response.status(Status.FORBIDDEN).build();
}
}
logger.info("proxying request to {}", queryUrl);

Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ public String getLabel(Widget widget) {
public void addSeparatorToNavbar(Page page, NavbarPositionType position, boolean ifNotEmpty) {
Navbar navbar = getNavbar(page, position);
if (navbar != null) {
if (!ifNotEmpty || navbar.getPageOrGroupOrLine().size() > 0) {
if (!ifNotEmpty || !navbar.getPageOrGroupOrLine().isEmpty()) {
Line line = new Line();
line.setLayout(createLayout(0));
navbar.getPageOrGroupOrLine().add(factory.createNavbarLine(line));
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
Expand All @@ -45,6 +44,7 @@
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.OpenHAB;
import org.openhab.core.items.Item;
Expand Down Expand Up @@ -72,6 +72,7 @@
*
* @author Tobias Bräutigam - Initial contribution
*/
@NonNullByDefault
public class CometVisuServlet extends HttpServlet {
private static final long serialVersionUID = 4448918908615003303L;
private final Logger logger = LoggerFactory.getLogger(CometVisuServlet.class);
Expand Down Expand Up @@ -138,6 +139,10 @@ public final void init(@Nullable ServletConfig config) throws ServletException {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
File requestedFile = getRequestedFile(req);
if (requestedFile == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}

String path = req.getPathInfo();
if (path == null) {
Expand Down Expand Up @@ -168,6 +173,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
}
}
}
if (requestedFile.getName().equalsIgnoreCase("version")) {
// tell client that its been served by openhab
resp.setHeader("X-CometVisu-Backend-Name", "openhab");
}
if (requestedFile.getName().equalsIgnoreCase("hidden.php")) {
// do not deliver the hidden php
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
Expand All @@ -178,7 +187,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
}
}

protected File getRequestedFile(HttpServletRequest req) throws UnsupportedEncodingException {
protected @Nullable File getRequestedFile(HttpServletRequest req) throws IOException {
String requestedFile = req.getPathInfo();
File file = null;

Expand All @@ -188,12 +197,18 @@ protected File getRequestedFile(HttpServletRequest req) throws UnsupportedEncodi
requestedFile = requestedFile.substring(0, requestedFile.length() - 1);
}
file = new File(userFileFolder, URLDecoder.decode(requestedFile, StandardCharsets.UTF_8));
if (!file.getCanonicalPath().startsWith(userFileFolder.getCanonicalPath() + File.separator)) {
return null;
}
}
// serve the file from the cometvisu src directory
if (file == null || !file.exists() || file.isDirectory()) {
file = requestedFile != null
? new File(rootFolder, URLDecoder.decode(requestedFile, StandardCharsets.UTF_8))
: rootFolder;
if (!file.getCanonicalPath().startsWith(rootFolder.getCanonicalPath() + File.separator)) {
return null;
}
}
if (file.isDirectory()) {
// search for an index file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ public FsEntry getEntry(MountedFile mfile, boolean recursive) {
}

public static Response createErrorResponse(FileOperationException e) {
return FsUtil.createErrorResponse(e.getStatus(), e.getCause().toString());
return FsUtil.createErrorResponse(e.getStatus(), e.getMessage());
}

public static Response createErrorResponse(Status status, String message) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public boolean exists() {
return toFile().exists();
}

public boolean canWrite() {
return toFile().canWrite();
}

public boolean isDirectory() {
return toFile().isDirectory();
}
Expand Down

This file was deleted.