CCM NG: Part of implementing access to themes via WebDAV
git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5434 8810af33-2d31-482b-a856-94f89814c4df
Former-commit-id: e09fa0df21
pull/2/head
parent
f5686c34dc
commit
227b626821
|
|
@ -28,21 +28,32 @@ import org.reflections.scanners.ResourcesScanner;
|
||||||
import org.reflections.util.ClasspathHelper;
|
import org.reflections.util.ClasspathHelper;
|
||||||
import org.reflections.util.ConfigurationBuilder;
|
import org.reflections.util.ConfigurationBuilder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.enterprise.context.RequestScoped;
|
import javax.enterprise.context.RequestScoped;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.json.Json;
|
||||||
|
import javax.json.JsonArray;
|
||||||
|
import javax.json.JsonObject;
|
||||||
|
import javax.json.JsonReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -67,6 +78,9 @@ public class StaticThemeProvider implements ThemeProvider {
|
||||||
@Inject
|
@Inject
|
||||||
private ThemeManifestUtil manifestUtil;
|
private ThemeManifestUtil manifestUtil;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ThemeFileInfoUtil themeFileInfoUtil;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ThemeInfo> getThemes() {
|
public List<ThemeInfo> getThemes() {
|
||||||
|
|
||||||
|
|
@ -248,7 +262,30 @@ public class StaticThemeProvider implements ThemeProvider {
|
||||||
final ThemeVersion version,
|
final ThemeVersion version,
|
||||||
final String path) {
|
final String path) {
|
||||||
|
|
||||||
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
Objects.requireNonNull(theme);
|
||||||
|
Objects.requireNonNull(version);
|
||||||
|
Objects.requireNonNull(path);
|
||||||
|
|
||||||
|
if (theme.isEmpty() || theme.matches("\\s*")) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"The name of the theme can't be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final String[] pathTokens = path.split("/");
|
||||||
|
|
||||||
|
final String indexFilePath = String.format("/"
|
||||||
|
+ THEMES_PACKAGE
|
||||||
|
+ "/%s/theme-index.json",
|
||||||
|
theme);
|
||||||
|
final InputStream stream = getClass()
|
||||||
|
.getResourceAsStream(indexFilePath);
|
||||||
|
final JsonReader reader = Json.createReader(stream);
|
||||||
|
final JsonObject indexObj = reader.readObject();
|
||||||
|
final JsonArray filesArray = indexObj.getJsonArray("files");
|
||||||
|
filesArray
|
||||||
|
.forEach(value -> LOGGER.warn(value.toString()));
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -289,12 +326,12 @@ public class StaticThemeProvider implements ThemeProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStreamForThemeFile(final String theme,
|
public OutputStream getOutputStreamForThemeFile(final String theme,
|
||||||
final String path) {
|
final String path) {
|
||||||
|
|
||||||
throw new UnsupportedOperationException(String
|
throw new UnsupportedOperationException(String
|
||||||
.format("This implementation of %s interface does not support "
|
.format("This implementation of %s interface does not support "
|
||||||
+ "changes to the theme files.",
|
+ "changes to the theme files.",
|
||||||
ThemeProvider.class.getName()));
|
ThemeProvider.class.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,4 +350,33 @@ public class StaticThemeProvider implements ThemeProvider {
|
||||||
//No op in this implementation.
|
//No op in this implementation.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private URI getJarUri() {
|
||||||
|
|
||||||
|
LOGGER.debug("Getting URI of JAR...");
|
||||||
|
|
||||||
|
final String themesUrl = getClass().getResource(THEMES_DIR).toString();
|
||||||
|
LOGGER.debug("Full URL of " + THEMES_DIR + " directory: {}", themesUrl);
|
||||||
|
|
||||||
|
final int index = themesUrl.indexOf('!');
|
||||||
|
final String pathToJar = themesUrl.substring(0, index);
|
||||||
|
|
||||||
|
final URI uri = URI.create(pathToJar);
|
||||||
|
LOGGER.debug("URI to JAR is \"%s\".", uri.toString());
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTheme(final Path path) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(path);
|
||||||
|
|
||||||
|
if (!Files.isDirectory(path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Path manifestPathJson = path.resolve(THEME_MANIFEST_JSON);
|
||||||
|
final Path manifestPathXml = path.resolve(THEME_MANIFEST_XML);
|
||||||
|
|
||||||
|
return Files.exists(manifestPathJson) || Files.exists(manifestPathXml);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2018 LibreCCM Foundation.
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
package org.libreccm.theming;
|
|
||||||
|
|
||||||
import org.libreccm.webdav.methods.PROPFIND;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
|
|
||||||
import javax.enterprise.context.RequestScoped;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.NotFoundException;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.PathParam;
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
|
||||||
*/
|
|
||||||
@RequestScoped
|
|
||||||
@Path("/dav/{theme}")
|
|
||||||
public class ThemeFilesDav {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private Themes themes;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/{path}")
|
|
||||||
public Response getFile(@PathParam("theme") final String theme,
|
|
||||||
@PathParam("path") final String path) {
|
|
||||||
|
|
||||||
final ThemeInfo info = themes
|
|
||||||
.getTheme(theme, ThemeVersion.LIVE)
|
|
||||||
.orElseThrow(() -> new NotFoundException(String
|
|
||||||
.format("Theme \"%s\" does not exist.", theme)));
|
|
||||||
|
|
||||||
final InputStream inputStream = themes
|
|
||||||
.getFileFromTheme(info, path)
|
|
||||||
.orElseThrow(() -> new NotFoundException(String
|
|
||||||
.format("The file \"%s\" does exist in the theme \"%s\".",
|
|
||||||
path,
|
|
||||||
theme)));
|
|
||||||
|
|
||||||
final MediaType mediaType = getMediaTypeFromPath(path);
|
|
||||||
|
|
||||||
final BufferedReader reader = new BufferedReader(
|
|
||||||
new InputStreamReader(inputStream));
|
|
||||||
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
||||||
try {
|
|
||||||
int value = reader.read();
|
|
||||||
while (value != -1) {
|
|
||||||
outputStream.write(value);
|
|
||||||
value = reader.read();
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new WebApplicationException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
final byte[] data = outputStream.toByteArray();
|
|
||||||
return Response.ok(data, mediaType).build();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@PROPFIND
|
|
||||||
@Path("/{path}")
|
|
||||||
public Response propfind(@PathParam("theme") final String theme,
|
|
||||||
@PathParam("path") final String path) {
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private MediaType getMediaTypeFromPath(final String path) {
|
|
||||||
|
|
||||||
if (path.endsWith(".css")) {
|
|
||||||
return new MediaType("text", "css");
|
|
||||||
} else {
|
|
||||||
return MediaType.WILDCARD_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -194,5 +194,36 @@ public class Themes implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all files in a theme at the specified path
|
||||||
|
*
|
||||||
|
* @param theme The theme from which the file is retrieved.
|
||||||
|
* @param path The path of the file relative to the root directory of the
|
||||||
|
* theme.
|
||||||
|
* @return A list of all files in the provided directory. If there is no
|
||||||
|
* such path in the theme the list is empty. If the path is the path
|
||||||
|
* of a file and not a directory the list should have one element,
|
||||||
|
* the data about the file itself.
|
||||||
|
*/
|
||||||
|
public List<ThemeFileInfo> listThemesFiles(final ThemeInfo theme,
|
||||||
|
final String path) {
|
||||||
|
|
||||||
|
final Instance<? extends ThemeProvider> forTheme = providers.select(
|
||||||
|
theme.getProvider());
|
||||||
|
|
||||||
|
if (forTheme.isUnsatisfied()) {
|
||||||
|
LOGGER.error("ThemeProvider \"{}\" not found.",
|
||||||
|
theme.getProvider().getName());
|
||||||
|
throw new UnexpectedErrorException(String.format(
|
||||||
|
"ThemeProvider \"%s\" not found.",
|
||||||
|
theme.getProvider().getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final ThemeProvider provider = forTheme.get();
|
||||||
|
return provider.listThemeFiles(theme.getName(),
|
||||||
|
theme.getVersion(),
|
||||||
|
path);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ public class ThemesService extends Application {
|
||||||
|
|
||||||
final Set<Class<?>> classes = new HashSet<>();
|
final Set<Class<?>> classes = new HashSet<>();
|
||||||
classes.add(ThemeFiles.class);
|
classes.add(ThemeFiles.class);
|
||||||
classes.add(ThemeFilesDav.class);
|
|
||||||
|
|
||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 LibreCCM Foundation.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.libreccm.theming.webdav;
|
||||||
|
|
||||||
|
import org.libreccm.theming.ThemeFileInfo;
|
||||||
|
import org.libreccm.webdav.methods.PROPFIND;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
import javax.enterprise.context.RequestScoped;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.WebApplicationException;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import org.libreccm.theming.ThemeInfo;
|
||||||
|
import org.libreccm.theming.ThemeVersion;
|
||||||
|
import org.libreccm.theming.Themes;
|
||||||
|
import org.libreccm.webdav.ResponseStatus;
|
||||||
|
import org.libreccm.webdav.xml.elements.HRef;
|
||||||
|
import org.libreccm.webdav.xml.elements.MultiStatus;
|
||||||
|
import org.libreccm.webdav.xml.elements.Prop;
|
||||||
|
import org.libreccm.webdav.xml.elements.PropStat;
|
||||||
|
import org.libreccm.webdav.xml.elements.Status;
|
||||||
|
import org.libreccm.webdav.xml.elements.WebDavResponse;
|
||||||
|
import org.libreccm.webdav.xml.properties.DisplayName;
|
||||||
|
import org.libreccm.webdav.xml.properties.GetContentLength;
|
||||||
|
import org.libreccm.webdav.xml.properties.GetContentType;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.ws.rs.OPTIONS;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import javax.ws.rs.ext.Providers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
||||||
|
*/
|
||||||
|
@RequestScoped
|
||||||
|
@Path("/{theme}")
|
||||||
|
public class ThemeFiles {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Themes themes;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/{path}")
|
||||||
|
public Response getFile(@PathParam("theme") final String theme,
|
||||||
|
@PathParam("path") final String path) {
|
||||||
|
|
||||||
|
final ThemeInfo info = themes
|
||||||
|
.getTheme(theme, ThemeVersion.LIVE)
|
||||||
|
.orElseThrow(() -> new NotFoundException(String
|
||||||
|
.format("Theme \"%s\" does not exist.", theme)));
|
||||||
|
|
||||||
|
final InputStream inputStream = themes
|
||||||
|
.getFileFromTheme(info, path)
|
||||||
|
.orElseThrow(() -> new NotFoundException(String
|
||||||
|
.format("The file \"%s\" does exist in the theme \"%s\".",
|
||||||
|
path,
|
||||||
|
theme)));
|
||||||
|
|
||||||
|
final MediaType mediaType = getMediaTypeFromPath(path);
|
||||||
|
|
||||||
|
final BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(inputStream));
|
||||||
|
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
int value = reader.read();
|
||||||
|
while (value != -1) {
|
||||||
|
outputStream.write(value);
|
||||||
|
value = reader.read();
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new WebApplicationException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] data = outputStream.toByteArray();
|
||||||
|
return Response.ok(data, mediaType).build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@OPTIONS
|
||||||
|
public Response options() {
|
||||||
|
|
||||||
|
return Response
|
||||||
|
.noContent()
|
||||||
|
.header("DAV", "1, 2")
|
||||||
|
.header("Allow",
|
||||||
|
"GET,DELETE,MOVE,COPY,PROPFIND,OPTIONS,HEAD,PUT,PROPPATCH,"
|
||||||
|
+ "LOCK,UNLOCK")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PROPFIND
|
||||||
|
@Path("/{path}")
|
||||||
|
public Response propfind(@PathParam("theme") final String theme,
|
||||||
|
@PathParam("path") final String path,
|
||||||
|
@Context final UriInfo uriInfo,
|
||||||
|
@Context final Providers providers) {
|
||||||
|
|
||||||
|
final ThemeInfo themeInfo = themes
|
||||||
|
.getTheme(theme, ThemeVersion.DRAFT)
|
||||||
|
.orElseThrow(() -> new NotFoundException(String.format(
|
||||||
|
"No theme with name \"%s\" exists.",
|
||||||
|
theme)));
|
||||||
|
|
||||||
|
final List<ThemeFileInfo> fileInfos = themes.listThemesFiles(themeInfo,
|
||||||
|
path);
|
||||||
|
final MultiStatus result;
|
||||||
|
if (fileInfos.size() == 1) {
|
||||||
|
|
||||||
|
final ThemeFileInfo fileInfo = fileInfos.get(1);
|
||||||
|
|
||||||
|
result = new MultiStatus(buildWebDavResponse(fileInfo, uriInfo));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
final WebDavResponse folder = new WebDavResponse(
|
||||||
|
new HRef(uriInfo.getRequestUri()),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new PropStat(new Prop(new DisplayName(path),
|
||||||
|
org.libreccm.webdav.xml.elements.Collection.COLLECTION),
|
||||||
|
new Status(javax.ws.rs.core.Response.Status.OK)));
|
||||||
|
|
||||||
|
final List<WebDavResponse> responses = new LinkedList<>();
|
||||||
|
responses.add(folder);
|
||||||
|
|
||||||
|
final List<WebDavResponse> fileResponses = fileInfos
|
||||||
|
.stream()
|
||||||
|
.map(fileInfo -> buildWebDavResponse(fileInfo, uriInfo))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
responses.addAll(fileResponses);
|
||||||
|
|
||||||
|
result = new MultiStatus(responses.toArray(new WebDavResponse[]{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response
|
||||||
|
.status(ResponseStatus.MULTI_STATUS)
|
||||||
|
.entity(result)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebDavResponse buildWebDavResponse(final ThemeFileInfo fileInfo,
|
||||||
|
final UriInfo uriInfo) {
|
||||||
|
|
||||||
|
final PropStat propStat = new PropStat(
|
||||||
|
new Prop(new DisplayName(fileInfo.getName()),
|
||||||
|
new GetContentLength(fileInfo.getSize()),
|
||||||
|
new GetContentType(fileInfo.getMimeType())),
|
||||||
|
new Status(javax.ws.rs.core.Response.Status.OK));
|
||||||
|
|
||||||
|
final WebDavResponse response = new WebDavResponse(
|
||||||
|
new HRef(uriInfo.getRequestUri()),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
propStat);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaType getMediaTypeFromPath(final String path) {
|
||||||
|
|
||||||
|
if (path.endsWith(".css")) {
|
||||||
|
return new MediaType("text", "css");
|
||||||
|
} else {
|
||||||
|
return MediaType.WILDCARD_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 LibreCCM Foundation.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.libreccm.theming.webdav;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.ws.rs.ApplicationPath;
|
||||||
|
import javax.ws.rs.core.Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
||||||
|
*/
|
||||||
|
@ApplicationPath("/DAV/themes")
|
||||||
|
public class WebDAV extends Application {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Class<?>> getClasses() {
|
||||||
|
|
||||||
|
final Set<Class<?>> classes = new HashSet<>();
|
||||||
|
classes.add(ThemeFiles.class);
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 LibreCCM Foundation.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
package org.libreccm.webdav;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.annotation.WebFilter;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
||||||
|
*/
|
||||||
|
@WebFilter(filterName = "WebDAVAuthFilter", urlPatterns = {"/DAV/*"})
|
||||||
|
public class WebDAVAuthFilter implements Filter {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager
|
||||||
|
.getLogger(WebDAVAuthFilter.class);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(final FilterConfig filterConfig) throws ServletException {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(final ServletRequest request,
|
||||||
|
final ServletResponse response,
|
||||||
|
final FilterChain chain) throws IOException,
|
||||||
|
ServletException {
|
||||||
|
|
||||||
|
LOGGER.warn("Filtering");
|
||||||
|
|
||||||
|
if (subject.isAuthenticated()) {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
final HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
|
final HttpServletResponse httpResponse
|
||||||
|
= (HttpServletResponse) response;
|
||||||
|
|
||||||
|
final String authHeader = httpRequest.getHeader("Authorization");
|
||||||
|
if (authHeader == null) {
|
||||||
|
|
||||||
|
sendUnauthorizedResponse(httpResponse, "Authorization required");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
final StringTokenizer tokenizer
|
||||||
|
= new StringTokenizer(authHeader);
|
||||||
|
if (tokenizer.hasMoreTokens()) {
|
||||||
|
|
||||||
|
final String authMethod = tokenizer.nextToken();
|
||||||
|
if ("basic".equalsIgnoreCase(authMethod)) {
|
||||||
|
|
||||||
|
authenticate(httpRequest,
|
||||||
|
httpResponse,
|
||||||
|
chain,
|
||||||
|
tokenizer.nextToken());
|
||||||
|
} else {
|
||||||
|
sendUnauthorizedResponse(httpResponse,
|
||||||
|
"Unsupported authentication method");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sendUnauthorizedResponse(httpResponse,
|
||||||
|
"Failed to read authentication header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void authenticate(final HttpServletRequest request,
|
||||||
|
final HttpServletResponse response,
|
||||||
|
final FilterChain chain,
|
||||||
|
final String credentials)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
final Optional<UsernamePasswordToken> usernamePasswordToken
|
||||||
|
= readCredentials(
|
||||||
|
credentials);
|
||||||
|
if (usernamePasswordToken.isPresent()) {
|
||||||
|
try {
|
||||||
|
subject.login(usernamePasswordToken.get());
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
} catch (AuthenticationException ex) {
|
||||||
|
LOGGER.warn("Authentication failed for "
|
||||||
|
+ "subject \"{}\"",
|
||||||
|
usernamePasswordToken
|
||||||
|
.get()
|
||||||
|
.getUsername());
|
||||||
|
sendUnauthorizedResponse(response,
|
||||||
|
"Authentication failed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendUnauthorizedResponse(response,
|
||||||
|
"Invalid authentication token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<UsernamePasswordToken> readCredentials(
|
||||||
|
final String credentialsBase64) {
|
||||||
|
|
||||||
|
final String credentials = new String(
|
||||||
|
Base64
|
||||||
|
.getDecoder()
|
||||||
|
.decode(credentialsBase64));
|
||||||
|
final int pos = credentials.indexOf(":");
|
||||||
|
if (pos == -1) {
|
||||||
|
return Optional.empty();
|
||||||
|
} else {
|
||||||
|
final String username = credentials
|
||||||
|
.substring(0, pos)
|
||||||
|
.trim();
|
||||||
|
final String password = credentials
|
||||||
|
.substring(pos + 1);
|
||||||
|
|
||||||
|
return Optional.of(new UsernamePasswordToken(username, password));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendUnauthorizedResponse(final HttpServletResponse response,
|
||||||
|
final String message)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
response.setHeader("WWW-Authenticate",
|
||||||
|
"Basic realm=\"LibreCCM_WebDAV\"");
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
|
||||||
|
message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -50,7 +50,7 @@ import javax.xml.bind.annotation.XmlType;
|
||||||
public final class MultiStatus {
|
public final class MultiStatus {
|
||||||
|
|
||||||
@XmlElement(name = "response")
|
@XmlElement(name = "response")
|
||||||
private final List<Response> responses;
|
private final List<WebDavResponse> responses;
|
||||||
|
|
||||||
@XmlElement(name = "responsedescription")
|
@XmlElement(name = "responsedescription")
|
||||||
private final ResponseDescription responseDescription;
|
private final ResponseDescription responseDescription;
|
||||||
|
|
@ -61,7 +61,7 @@ public final class MultiStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiStatus(final ResponseDescription responseDescription,
|
public MultiStatus(final ResponseDescription responseDescription,
|
||||||
final Response... responses) {
|
final WebDavResponse... responses) {
|
||||||
|
|
||||||
if (responses == null || responses.length == 0) {
|
if (responses == null || responses.length == 0) {
|
||||||
this.responses = Collections.emptyList();
|
this.responses = Collections.emptyList();
|
||||||
|
|
@ -72,15 +72,15 @@ public final class MultiStatus {
|
||||||
this.responseDescription = responseDescription;
|
this.responseDescription = responseDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiStatus(final Response... responses) {
|
public MultiStatus(final WebDavResponse... responses) {
|
||||||
this(null, responses);
|
this(null, responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiStatus(final ResponseDescription responseDescription) {
|
public MultiStatus(final ResponseDescription responseDescription) {
|
||||||
this(responseDescription, (Response[]) null);
|
this(responseDescription, (WebDavResponse[]) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final List<Response> getResponses() {
|
public final List<WebDavResponse> getResponses() {
|
||||||
return Collections.unmodifiableList(responses);
|
return Collections.unmodifiableList(responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,8 @@ import static javax.xml.bind.annotation.XmlAccessType.*;
|
||||||
@XmlAccessorType(FIELD)
|
@XmlAccessorType(FIELD)
|
||||||
@XmlType(propOrder = {"hRefs", "status", "propStats", "error",
|
@XmlType(propOrder = {"hRefs", "status", "propStats", "error",
|
||||||
"responseDescription", "location"})
|
"responseDescription", "location"})
|
||||||
@XmlRootElement
|
@XmlRootElement(name = "response")
|
||||||
public final class Response {
|
public final class WebDavResponse {
|
||||||
|
|
||||||
@XmlElement(name = "href")
|
@XmlElement(name = "href")
|
||||||
private final List<HRef> hRefs;
|
private final List<HRef> hRefs;
|
||||||
|
|
@ -70,7 +70,7 @@ public final class Response {
|
||||||
private Location location;
|
private Location location;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private Response() {
|
private WebDavResponse() {
|
||||||
this(new LinkedList<HRef>(),
|
this(new LinkedList<HRef>(),
|
||||||
null,
|
null,
|
||||||
new LinkedList<PropStat>(),
|
new LinkedList<PropStat>(),
|
||||||
|
|
@ -79,7 +79,7 @@ public final class Response {
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response(final List<HRef> hRefs,
|
private WebDavResponse(final List<HRef> hRefs,
|
||||||
final Status status,
|
final Status status,
|
||||||
final List<PropStat> propStats,
|
final List<PropStat> propStats,
|
||||||
final Error error,
|
final Error error,
|
||||||
|
|
@ -93,7 +93,7 @@ public final class Response {
|
||||||
this.location = location;
|
this.location = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response(final HRef hRef,
|
public WebDavResponse(final HRef hRef,
|
||||||
final Error error,
|
final Error error,
|
||||||
final ResponseDescription responseDescription,
|
final ResponseDescription responseDescription,
|
||||||
final Location location,
|
final Location location,
|
||||||
|
|
@ -107,7 +107,7 @@ public final class Response {
|
||||||
responseDescription, location);
|
responseDescription, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response(final Status status,
|
public WebDavResponse(final Status status,
|
||||||
final Error error,
|
final Error error,
|
||||||
final ResponseDescription responseDescription,
|
final ResponseDescription responseDescription,
|
||||||
final Location location,
|
final Location location,
|
||||||
|
|
@ -175,10 +175,10 @@ public final class Response {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(obj instanceof Response)) {
|
if (!(obj instanceof WebDavResponse)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final Response other = (Response) obj;
|
final WebDavResponse other = (WebDavResponse) obj;
|
||||||
if (!Objects.equals(hRefs, other.getHRefs())) {
|
if (!Objects.equals(hRefs, other.getHRefs())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "foo",
|
||||||
|
"isDirectory": true,
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "bar",
|
||||||
|
"isDirectory": true,
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "theme.json",
|
||||||
|
"isDirectory": false,
|
||||||
|
"mimeType": "application/json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "theme.xml",
|
||||||
|
"isDirectory": false,
|
||||||
|
"mimeType": "text/xml"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category-page.xsl",
|
||||||
|
"isDirectory": false,
|
||||||
|
"mimeType": "application/xml"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "footer.xsl",
|
||||||
|
"isDirectory": false,
|
||||||
|
"mimeType": "application/xml"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "settings.properties",
|
||||||
|
"isDirectory": false,
|
||||||
|
"mimeType": "text/plain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "style.css",
|
||||||
|
"isDirectory": false,
|
||||||
|
"mimeType": "text/css"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "theme-bundle.properties",
|
||||||
|
"isDirectory": false,
|
||||||
|
"mimeType": "text/plain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "texts",
|
||||||
|
"isDirectory": true,
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"name": "labels.properties",
|
||||||
|
"isDirectory": false,
|
||||||
|
"mimeType": "text/x-java-properties"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"author": "Jens Pelzetter",
|
"author": "Jens Pelzetter",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack",
|
"build": "tsc",
|
||||||
"tslint": "tslint --project ."
|
"tslint": "tslint --project ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,33 @@
|
||||||
<resource>
|
<resource>
|
||||||
<directory>src/main/typescript</directory>
|
<directory>src/main/typescript</directory>
|
||||||
</resource>
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.build.directory}/generated-resources</directory>
|
||||||
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-remote-resources-plugin</artifactId>
|
||||||
|
<version>1.5</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>bundle</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<resourcesDirectory>src/main/typescript</resourcesDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*.ts</include>
|
||||||
|
<include>**/*.tsx</include>
|
||||||
|
</includes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.github.eirslett</groupId>
|
<groupId>com.github.eirslett</groupId>
|
||||||
<artifactId>frontend-maven-plugin</artifactId>
|
<artifactId>frontend-maven-plugin</artifactId>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
"jsx": "React",
|
"jsx": "React",
|
||||||
"module": "amd",
|
"module": "amd",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
"outDir": "target/generated-resources/dist",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"target": "es6"
|
"target": "es6"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, "src/main/resources/dist"),
|
//path: path.resolve(__dirname, "src/main/resources/dist"),
|
||||||
|
path: path.resolve(__dirname, "target/generated-resources/dist"),
|
||||||
filename: "ccm-pagemodelseditor.js"
|
filename: "ccm-pagemodelseditor.js"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
4
pom.xml
4
pom.xml
|
|
@ -84,6 +84,8 @@
|
||||||
<!-- Archetypes -->
|
<!-- Archetypes -->
|
||||||
<module>ccm-archetype-module</module>
|
<module>ccm-archetype-module</module>
|
||||||
<module>ccm-cms-archetype-contenttype</module>
|
<module>ccm-cms-archetype-contenttype</module>
|
||||||
|
|
||||||
|
<!-- <module>ccm-cms-js</module>-->
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<reporting>
|
<reporting>
|
||||||
|
|
@ -777,4 +779,4 @@
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
Loading…
Reference in New Issue