CcmNG: Backend for Theme editing

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5693 8810af33-2d31-482b-a856-94f89814c4df

Former-commit-id: 5880af452e
pull/2/head
jensp 2018-09-19 18:14:14 +00:00
parent d4e9a5f27f
commit d1fbf22512
13 changed files with 520 additions and 47 deletions

View File

@ -187,6 +187,19 @@
<artifactId>shiro-web</artifactId> <artifactId>shiro-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>

View File

@ -78,6 +78,9 @@ public final class KernelConfig {
@Setting @Setting
private String importPath = ""; private String importPath = "";
@Setting
private String jwtSecret = "";
public static KernelConfig getConfig() { public static KernelConfig getConfig() {
final ConfigurationManager confManager = CdiUtil.createCdiUtil() final ConfigurationManager confManager = CdiUtil.createCdiUtil()
@ -233,6 +236,14 @@ public final class KernelConfig {
public void setImportPath(final String importPath) { public void setImportPath(final String importPath) {
this.importPath = importPath; this.importPath = importPath;
} }
public String getJwtSecret() {
return jwtSecret;
}
public void setJwtSecret(final String jwtSecret) {
this.jwtSecret = jwtSecret;
}
@Override @Override
public int hashCode() { public int hashCode() {
@ -249,6 +260,7 @@ public final class KernelConfig {
hash = 61 * hash + Objects.hashCode(systemEmailAddress); hash = 61 * hash + Objects.hashCode(systemEmailAddress);
hash = 61 * hash + Objects.hashCode(exportPath); hash = 61 * hash + Objects.hashCode(exportPath);
hash = 61 * hash + Objects.hashCode(importPath); hash = 61 * hash + Objects.hashCode(importPath);
hash = 61 * hash * Objects.hashCode(jwtSecret);
return hash; return hash;
} }
@ -304,7 +316,11 @@ public final class KernelConfig {
return false; return false;
} }
return Objects.equals(importPath, other.getImportPath()); if (!Objects.equals(importPath, other.getImportPath())) {
return false;
}
return Objects.equals(jwtSecret, other.getJwtSecret());
} }
@Override @Override

View File

@ -47,6 +47,8 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Properties; import java.util.Properties;
import javax.transaction.Transactional;
/** /**
* *
@ -143,6 +145,7 @@ public class CcmCore implements CcmModule {
@Override @Override
public void init(final InitEvent event) { public void init(final InitEvent event) {
// Nothing
} }
@Override @Override

View File

@ -316,8 +316,11 @@ public class CcmFiles {
public boolean existsFile(final String path) public boolean existsFile(final String path)
throws FileAccessException, throws FileAccessException,
InsufficientPermissionsException { InsufficientPermissionsException {
final String dataPath = getDataPath(path);
final boolean result = getFileSystemAdapter().existsFile(dataPath);
return getFileSystemAdapter().existsFile(getDataPath(path)); return result;
} }
/** /**

View File

@ -381,7 +381,8 @@ public class NIOFileSystemAdapter implements FileSystemAdapter {
final Path nioPath = Paths.get(path); final Path nioPath = Paths.get(path);
if (!Files.isDirectory(nioPath)) { if (!Files.isDirectory(nioPath)) {
throw new FileAccessException(path); throw new FileAccessException(String.format(
"%s is not a directory.", path));
} }
final Stream<Path> paths; final Stream<Path> paths;

View File

@ -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.security;
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("/jwt")
public class CcmJwtAuthentication extends Application{
@Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(JwtProvider.class);
return classes;
}
}

View File

@ -0,0 +1,158 @@
/*
* 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.security;
import com.arsdigita.kernel.KernelConfig;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import io.jsonwebtoken.SignatureAlgorithm;
import org.libreccm.configuration.ConfigurationManager;
import java.io.StringReader;
import java.security.Key;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Random;
import javax.crypto.spec.SecretKeySpec;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
import javax.transaction.Transactional;
import javax.ws.rs.POST;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path("/")
public class JwtProvider {
@Inject
private ConfigurationManager confManager;
@Inject
private Shiro shiro;
@POST
@Path("/")
@Transactional(Transactional.TxType.REQUIRED)
public Response getJsonWebToken(final String requestCredentials) {
if (requestCredentials == null) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("No credentials provided")
.build();
}
final JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();
final StringReader credentialsReader = new StringReader(
requestCredentials);
final JsonReader jsonReader = Json.createReader(credentialsReader);
final JsonObject credentials = jsonReader.readObject();
final String userName = credentials.getString("username", null);
final String password = credentials.getString("password", null);
if (userName == null
|| userName.isEmpty()
|| userName.matches("\\s*")) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("No user name was provided")
.build();
}
if (password == null
|| password.isEmpty()
|| password.matches("\\s*")) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("No password was provided")
.build();
}
final UsernamePasswordToken token = new UsernamePasswordToken(
userName, password);
final Subject subject = shiro.getSubject();
final KernelConfig kernelConfig = confManager
.findConfiguration(KernelConfig.class);
if (kernelConfig.getJwtSecret() == null
|| kernelConfig.getJwtSecret().isEmpty()
|| kernelConfig.getJwtSecret().matches("\\s*")) {
shiro.getSystemUser().execute(this::generateSecret);
}
try {
subject.login(token);
final SignatureAlgorithm signAlgo = SignatureAlgorithm.HS512;
final Key key = new SecretKeySpec(
Base64.getDecoder().decode(kernelConfig.getJwtSecret()),
signAlgo.getJcaName());
final JwtBuilder jwtBuilder = Jwts
.builder()
.setSubject((String) subject.getPrincipal())
.signWith(key);
return Response
.ok(jwtBuilder.compact())
.build();
} catch (AuthenticationException ex) {
return Response
.status(Response.Status.FORBIDDEN)
.build();
}
}
private void generateSecret() {
final Random random = new SecureRandom();
final byte[] randomBytes = new byte[64];
random.nextBytes(randomBytes);
final String secret = Base64
.getEncoder()
.encodeToString(randomBytes);
final KernelConfig kernelConfig = confManager
.findConfiguration(KernelConfig.class);
kernelConfig.setJwtSecret(secret);
confManager.saveConfiguration(kernelConfig);
}
}

View File

@ -33,6 +33,7 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -125,8 +126,7 @@ public class FileSystemThemeProvider implements ThemeProvider {
public Optional<ThemeInfo> getThemeInfo(final String theme, public Optional<ThemeInfo> getThemeInfo(final String theme,
final ThemeVersion version) { final ThemeVersion version) {
final String themePath = createThemePath(theme, version); return readInfo(theme);
return readInfo(themePath);
} }
@Override @Override
@ -228,15 +228,23 @@ public class FileSystemThemeProvider implements ThemeProvider {
final String path) { final String path) {
final String themePath = createThemePath(theme, version); final String themePath = createThemePath(theme, version);
final String filePath = String.join(themePath, path, "/"); final String filePath = String.join("/", themePath, path);
try { try {
if (ccmFiles.isDirectory(filePath)) {
return ccmFiles
.listFiles(filePath)
.stream()
.map(currentPath -> buildThemeFileInfo(
String.join("/", theme, currentPath)))
.collect(Collectors.toList());
} else {
final List<ThemeFileInfo> result = new ArrayList<>();
final ThemeFileInfo fileInfo = buildThemeFileInfo(filePath);
result.add(fileInfo);
return ccmFiles return result;
.listFiles(filePath) }
.stream()
.map(currentPath -> buildThemeFileInfo(currentPath))
.collect(Collectors.toList());
} catch (FileAccessException } catch (FileAccessException
| FileDoesNotExistException | FileDoesNotExistException
@ -246,12 +254,32 @@ public class FileSystemThemeProvider implements ThemeProvider {
} }
} }
@Override
public Optional<ThemeFileInfo> getThemeFileInfo(
final String theme, final ThemeVersion version, final String path) {
final String themePath = createThemePath(theme, version);
final String filePath = String.join("/", themePath, path);
try {
if (ccmFiles.existsFile(filePath)) {
return Optional.of(buildThemeFileInfo(filePath));
} else {
return Optional.empty();
}
} catch (FileAccessException | InsufficientPermissionsException ex) {
throw new UnexpectedErrorException(ex);
}
}
@Override @Override
public Optional<InputStream> getThemeFileAsStream( public Optional<InputStream> getThemeFileAsStream(
final String theme, final ThemeVersion version, final String path) { final String theme, final ThemeVersion version, final String path) {
final String themePath = createThemePath(theme, version); final String themePath = createThemePath(theme, version);
final String filePath = String.join(themePath, path, "/"); final String filePath = String.join("/", themePath, path);
try { try {
if (ccmFiles.existsFile(path)) { if (ccmFiles.existsFile(path)) {
@ -272,7 +300,7 @@ public class FileSystemThemeProvider implements ThemeProvider {
final String path) { final String path) {
final String themePath = createThemePath(theme, ThemeVersion.DRAFT); final String themePath = createThemePath(theme, ThemeVersion.DRAFT);
final String filePath = String.join(themePath, path, "/"); final String filePath = String.join("/", themePath, path);
try { try {
@ -289,7 +317,7 @@ public class FileSystemThemeProvider implements ThemeProvider {
public void deleteThemeFile(final String theme, final String path) { public void deleteThemeFile(final String theme, final String path) {
final String themePath = createThemePath(theme, ThemeVersion.DRAFT); final String themePath = createThemePath(theme, ThemeVersion.DRAFT);
final String filePath = String.join(themePath, path, "/"); final String filePath = String.join("/", themePath, path);
try { try {
ccmFiles.deleteFile(filePath, true); ccmFiles.deleteFile(filePath, true);
@ -383,15 +411,15 @@ public class FileSystemThemeProvider implements ThemeProvider {
} }
} }
private Optional<ThemeInfo> readInfo(final String themePath) { private Optional<ThemeInfo> readInfo(final String themeName) {
final ThemeManifest manifest; final ThemeManifest manifest;
try { try {
final String jsonPath = String.format( final String jsonPath = String.format(
DRAFT_THEMES_PATH + "/" + THEME_JSON, themePath); DRAFT_THEMES_PATH + "/" + THEME_JSON, themeName);
final String xmlPath = String.format( final String xmlPath = String.format(
DRAFT_THEMES_PATH + "/" + THEME_XML, themePath); DRAFT_THEMES_PATH + "/" + THEME_XML, themeName);
if (ccmFiles.existsFile(jsonPath)) { if (ccmFiles.existsFile(jsonPath)) {
final InputStream inputStream = ccmFiles final InputStream inputStream = ccmFiles

View File

@ -256,20 +256,20 @@ public class StaticThemeProvider implements ThemeProvider {
@Override @Override
public ThemeInfo createTheme(final String theme) { public ThemeInfo createTheme(final String theme) {
throw new UnsupportedOperationException(String.format( throw new UnsupportedOperationException(String.format(
"The ThemeProvider %s does support the creation of new themes.", "The ThemeProvider %s does support the creation of new themes.",
getClass().getName())); getClass().getName()));
} }
@Override @Override
public void deleteTheme(final String theme) { public void deleteTheme(final String theme) {
throw new UnsupportedOperationException(String.format( throw new UnsupportedOperationException(String.format(
"The ThemeProvider %s does support the deltion of themes.", "The ThemeProvider %s does support the deltion of themes.",
getClass().getName())); getClass().getName()));
} }
@Override @Override
public List<ThemeFileInfo> listThemeFiles(final String theme, public List<ThemeFileInfo> listThemeFiles(final String theme,
final ThemeVersion version, final ThemeVersion version,
@ -321,9 +321,37 @@ public class StaticThemeProvider implements ThemeProvider {
} }
@Override @Override
public Optional<InputStream> getThemeFileAsStream(final String theme, public Optional<ThemeFileInfo> getThemeFileInfo(
final ThemeVersion version, final String theme, final ThemeVersion version, final String path) {
final String path) {
Objects.requireNonNull(theme);
Objects.requireNonNull(version);
Objects.requireNonNull(path);
final List<String> pathTokens = Arrays.asList(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 currentDir = indexObj.getJsonArray("files");
final Optional<JsonObject> targetFile = findFile(pathTokens,
currentDir);
if (targetFile.isPresent()) {
return Optional.of(generateFileInfo(targetFile.get()));
} else {
return Optional.empty();
}
}
@Override
public Optional<InputStream> getThemeFileAsStream(
final String theme, final ThemeVersion version, final String path) {
Objects.requireNonNull(theme); Objects.requireNonNull(theme);
Objects.requireNonNull(path); Objects.requireNonNull(path);
@ -390,7 +418,7 @@ public class StaticThemeProvider implements ThemeProvider {
public void publishTheme(final String theme) { public void publishTheme(final String theme) {
//No op in this implementation. //No op in this implementation.
} }
@Override @Override
public void unpublishTheme(final String theme) { public void unpublishTheme(final String theme) {
//No op in this implementation. //No op in this implementation.

View File

@ -137,6 +137,21 @@ public interface ThemeProvider extends Serializable {
ThemeVersion version, ThemeVersion version,
String path); String path);
/**
* Retrieve the metadata for a particular file in the theme.
*
* @param theme The name of the theme.
* @param version The version of the theme
* @param path The path of the file, relative to the theme directory.
*
* @return An {@link Optional} containing a {@link ThemeInfo} object with
* the metadata of the file is its exists. Otherwise an empty
* {@link Optional}.
*/
Optional<ThemeFileInfo> getThemeFileInfo(String theme,
ThemeVersion version,
String path);
/** /**
* Retrieve a file from a theme. We use an {@link InputStream} here because * Retrieve a file from a theme. We use an {@link InputStream} here because
* that is the most universal interface in the Java API which works for all * that is the most universal interface in the Java API which works for all
@ -208,11 +223,11 @@ public interface ThemeProvider extends Serializable {
* @param theme The theme to publish. * @param theme The theme to publish.
*/ */
void publishTheme(String theme); void publishTheme(String theme);
/** /**
* Unpublishes (deletes) the live version of a theme. For * Unpublishes (deletes) the live version of a theme. For implementations
* implementations which do not support draft/live themes the implementation * which do not support draft/live themes the implementation of this method
* of this method should be a noop, but not throw an exception. * should be a noop, but not throw an exception.
* *
* @param theme The theme to publish. * @param theme The theme to publish.
*/ */

View File

@ -182,11 +182,29 @@ public class DatabaseThemeProvider implements ThemeProvider {
return result; return result;
} }
@Override
public Optional<ThemeFileInfo> getThemeFileInfo(
final String themeName, final ThemeVersion version, final String path) {
final Theme theme = themeRepository
.findThemeByName(path, version)
.orElseThrow(() -> new IllegalArgumentException(String
.format("No Theme \"%s\" in the database.", themeName)));
final Optional<ThemeFile> themeFile = fileRepository
.findByPath(theme, path, version);
if (themeFile.isPresent()) {
return Optional.of(createThemeFileInfo(themeFile.get()));
} else {
return Optional.empty();
}
}
@Override @Override
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public Optional<InputStream> getThemeFileAsStream(final String themeName, public Optional<InputStream> getThemeFileAsStream(
final ThemeVersion version, final String themeName, final ThemeVersion version, final String path) {
final String path) {
final Optional<Theme> theme = themeRepository final Optional<Theme> theme = themeRepository
.findThemeByName(themeName, version); .findThemeByName(themeName, version);
@ -325,10 +343,10 @@ public class DatabaseThemeProvider implements ThemeProvider {
.findThemeByName(themeName, ThemeVersion.DRAFT) .findThemeByName(themeName, ThemeVersion.DRAFT)
.ifPresent(themeManager::publishTheme); .ifPresent(themeManager::publishTheme);
} }
@Override @Override
public void unpublishTheme(final String themeName) { public void unpublishTheme(final String themeName) {
themeRepository themeRepository
.findThemeByName(themeName, ThemeVersion.LIVE) .findThemeByName(themeName, ThemeVersion.LIVE)
.ifPresent(themeManager::unpublishTheme); .ifPresent(themeManager::unpublishTheme);

View File

@ -20,12 +20,16 @@ package org.libreccm.theming.manager;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege; import org.libreccm.security.RequiresPrivilege;
import org.libreccm.theming.ThemeFileInfo;
import org.libreccm.theming.ThemeInfo; import org.libreccm.theming.ThemeInfo;
import org.libreccm.theming.ThemeProvider; import org.libreccm.theming.ThemeProvider;
import org.libreccm.theming.ThemeVersion; import org.libreccm.theming.ThemeVersion;
import org.libreccm.theming.ThemingPrivileges; import org.libreccm.theming.ThemingPrivileges;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable; import java.io.Serializable;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.ArrayList; import java.util.ArrayList;
@ -52,7 +56,6 @@ import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
/** /**
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
@ -75,7 +78,7 @@ public class Themes implements Serializable {
@GET @GET
@Path("/providers") @Path("/providers")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
//@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES) @RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public String getThemeProviders() { public String getThemeProviders() {
@ -103,7 +106,7 @@ public class Themes implements Serializable {
@GET @GET
@Path("/themes") @Path("/themes")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
//@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES) @RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public List<ThemeInfo> getAvailableThemes() { public List<ThemeInfo> getAvailableThemes() {
@ -120,7 +123,7 @@ public class Themes implements Serializable {
@GET @GET
@Path("/themes/{theme}") @Path("/themes/{theme}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
//@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME) @RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
public ThemeInfo getTheme(@PathParam("theme") final String themeName) { public ThemeInfo getTheme(@PathParam("theme") final String themeName) {
@ -140,7 +143,7 @@ public class Themes implements Serializable {
@Path("/themes/{theme}") @Path("/themes/{theme}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
//@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES) @RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public ThemeInfo createTheme( public ThemeInfo createTheme(
@PathParam("theme") final String themeName, @PathParam("theme") final String themeName,
@ -176,7 +179,7 @@ public class Themes implements Serializable {
@DELETE @DELETE
@Path("/themes/{theme}") @Path("/themes/{theme}")
//@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES) @RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public void deleteTheme(@PathParam("theme") final String themeName) { public void deleteTheme(@PathParam("theme") final String themeName) {
@ -195,7 +198,7 @@ public class Themes implements Serializable {
@POST @POST
@Path("/themes/{theme}/live") @Path("/themes/{theme}/live")
//@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES) @RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public void publishTheme(@PathParam("theme") final String themeName) { public void publishTheme(@PathParam("theme") final String themeName) {
@ -213,7 +216,7 @@ public class Themes implements Serializable {
@DELETE @DELETE
@Path("/themes/{theme}/live") @Path("/themes/{theme}/live")
//@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES) @RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public void unPublishTheme(@PathParam("theme") final String themeName) { public void unPublishTheme(@PathParam("theme") final String themeName) {
@ -226,7 +229,134 @@ public class Themes implements Serializable {
} else { } else {
throw new WebApplicationException(Response.Status.NOT_FOUND); throw new WebApplicationException(Response.Status.NOT_FOUND);
} }
}
@GET
@Path("/themes/{theme}/files/")
@AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public Response getThemeRootDir(@PathParam("theme") final String themeName) {
final Optional<ThemeProvider> provider = findProvider(themeName);
if (provider.isPresent()) {
final Optional<ThemeFileInfo> fileInfo = provider
.get()
.getThemeFileInfo(themeName, ThemeVersion.DRAFT, "/");
if (fileInfo.isPresent()) {
if (fileInfo.get().isDirectory()) {
final List<ThemeFileInfo> fileInfos = provider
.get()
.listThemeFiles(themeName, ThemeVersion.DRAFT, "/");
return Response
.ok(fileInfos)
.type(MediaType.APPLICATION_JSON)
.build();
} else {
throw new WebApplicationException(
String.format(
"File \"/\" in theme %s is not a directory.",
themeName),
Response.Status.INTERNAL_SERVER_ERROR);
}
} else {
return Response
.status(Response.Status.NOT_FOUND)
.entity(String.format(
"File \"/\" does not exist in theme %s.",
themeName))
.build();
}
} else {
return Response
.status(Response.Status.NOT_FOUND)
.entity(String.format("Theme \"%s\" does not exist.",
themeName))
.build();
}
}
@GET
@Path("/themes/{theme}/files/{path:.+}")
@AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public Response getThemeFile(@PathParam("theme") final String themeName,
@PathParam("path") final String path) {
final Optional<ThemeProvider> provider = findProvider(themeName);
if (provider.isPresent()) {
final Optional<ThemeFileInfo> fileInfo = provider
.get()
.getThemeFileInfo(themeName, ThemeVersion.DRAFT, path);
if (fileInfo.isPresent()) {
final ThemeFileInfo themeFileInfo = fileInfo.get();
if (themeFileInfo.isDirectory()) {
final List<ThemeFileInfo> fileInfos = provider
.get()
.listThemeFiles(themeName, ThemeVersion.DRAFT, path);
return Response
.ok(fileInfos)
.type(MediaType.APPLICATION_JSON)
.build();
} else {
final Optional<InputStream> inputStream = provider
.get()
.getThemeFileAsStream(themeName,
ThemeVersion.DRAFT,
path);
if (inputStream.isPresent()) {
final InputStream inStream = inputStream.get();
return Response
.ok(inStream)
.type(themeFileInfo.getMimeType())
.build();
} else {
return Response
.status(Response.Status.NOT_FOUND)
.entity(String.format(
"File \"%s\" does not exist in theme %s.",
path,
themeName))
.build();
}
}
} else {
return Response
.status(Response.Status.NOT_FOUND)
.entity(String.format(
"File \"%s\" does not exist in theme %s.",
path,
themeName))
.build();
}
} else {
return Response
.status(Response.Status.NOT_FOUND)
.entity(String.format("Theme \"%s\" does not exist.",
themeName))
.build();
}
} }
private String getProviderName(final ThemeProvider provider) { private String getProviderName(final ThemeProvider provider) {

17
pom.xml
View File

@ -462,6 +462,23 @@
<artifactId>shiro-web</artifactId> <artifactId>shiro-web</artifactId>
<version>1.3.2</version> <version>1.3.2</version>
</dependency> </dependency>
<!-- Json Web Token support -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
</dependency>
<!-- PrimeFaces for JSF prototype --> <!-- PrimeFaces for JSF prototype -->
<dependency> <dependency>