diff --git a/ccm-core/pom.xml b/ccm-core/pom.xml
index 66bb5898e..55af1630f 100644
--- a/ccm-core/pom.xml
+++ b/ccm-core/pom.xml
@@ -187,6 +187,19 @@
shiro-web
+
+ io.jsonwebtoken
+ jjwt-api
+
+
+ io.jsonwebtoken
+ jjwt-impl
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+
+
com.h2database
h2
diff --git a/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java b/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java
index 0e3315d28..8ea3b6144 100644
--- a/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java
+++ b/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java
@@ -78,6 +78,9 @@ public final class KernelConfig {
@Setting
private String importPath = "";
+
+ @Setting
+ private String jwtSecret = "";
public static KernelConfig getConfig() {
final ConfigurationManager confManager = CdiUtil.createCdiUtil()
@@ -233,6 +236,14 @@ public final class KernelConfig {
public void setImportPath(final String importPath) {
this.importPath = importPath;
}
+
+ public String getJwtSecret() {
+ return jwtSecret;
+ }
+
+ public void setJwtSecret(final String jwtSecret) {
+ this.jwtSecret = jwtSecret;
+ }
@Override
public int hashCode() {
@@ -249,6 +260,7 @@ public final class KernelConfig {
hash = 61 * hash + Objects.hashCode(systemEmailAddress);
hash = 61 * hash + Objects.hashCode(exportPath);
hash = 61 * hash + Objects.hashCode(importPath);
+ hash = 61 * hash * Objects.hashCode(jwtSecret);
return hash;
}
@@ -304,7 +316,11 @@ public final class KernelConfig {
return false;
}
- return Objects.equals(importPath, other.getImportPath());
+ if (!Objects.equals(importPath, other.getImportPath())) {
+ return false;
+ }
+
+ return Objects.equals(jwtSecret, other.getJwtSecret());
}
@Override
diff --git a/ccm-core/src/main/java/org/libreccm/core/CcmCore.java b/ccm-core/src/main/java/org/libreccm/core/CcmCore.java
index cc516ef62..684047481 100644
--- a/ccm-core/src/main/java/org/libreccm/core/CcmCore.java
+++ b/ccm-core/src/main/java/org/libreccm/core/CcmCore.java
@@ -47,6 +47,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
+import javax.transaction.Transactional;
+
/**
*
@@ -143,6 +145,7 @@ public class CcmCore implements CcmModule {
@Override
public void init(final InitEvent event) {
+ // Nothing
}
@Override
diff --git a/ccm-core/src/main/java/org/libreccm/files/CcmFiles.java b/ccm-core/src/main/java/org/libreccm/files/CcmFiles.java
index 41b7ab28a..b2a71a868 100644
--- a/ccm-core/src/main/java/org/libreccm/files/CcmFiles.java
+++ b/ccm-core/src/main/java/org/libreccm/files/CcmFiles.java
@@ -316,8 +316,11 @@ public class CcmFiles {
public boolean existsFile(final String path)
throws FileAccessException,
InsufficientPermissionsException {
+
+ final String dataPath = getDataPath(path);
+ final boolean result = getFileSystemAdapter().existsFile(dataPath);
- return getFileSystemAdapter().existsFile(getDataPath(path));
+ return result;
}
/**
diff --git a/ccm-core/src/main/java/org/libreccm/files/NIOFileSystemAdapter.java b/ccm-core/src/main/java/org/libreccm/files/NIOFileSystemAdapter.java
index 7c6b6c806..53278349d 100644
--- a/ccm-core/src/main/java/org/libreccm/files/NIOFileSystemAdapter.java
+++ b/ccm-core/src/main/java/org/libreccm/files/NIOFileSystemAdapter.java
@@ -381,7 +381,8 @@ public class NIOFileSystemAdapter implements FileSystemAdapter {
final Path nioPath = Paths.get(path);
if (!Files.isDirectory(nioPath)) {
- throw new FileAccessException(path);
+ throw new FileAccessException(String.format(
+ "%s is not a directory.", path));
}
final Stream paths;
diff --git a/ccm-core/src/main/java/org/libreccm/security/CcmJwtAuthentication.java b/ccm-core/src/main/java/org/libreccm/security/CcmJwtAuthentication.java
new file mode 100644
index 000000000..534f5cc23
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/security/CcmJwtAuthentication.java
@@ -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 Jens Pelzetter
+ */
+@ApplicationPath("/jwt")
+public class CcmJwtAuthentication extends Application{
+
+ @Override
+ public Set> getClasses() {
+
+ final Set> classes = new HashSet<>();
+ classes.add(JwtProvider.class);
+
+ return classes;
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/security/JwtProvider.java b/ccm-core/src/main/java/org/libreccm/security/JwtProvider.java
new file mode 100644
index 000000000..cb22aef9c
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/security/JwtProvider.java
@@ -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 Jens Pelzetter
+ */
+@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);
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/theming/FileSystemThemeProvider.java b/ccm-core/src/main/java/org/libreccm/theming/FileSystemThemeProvider.java
index 63f85e747..75b116550 100644
--- a/ccm-core/src/main/java/org/libreccm/theming/FileSystemThemeProvider.java
+++ b/ccm-core/src/main/java/org/libreccm/theming/FileSystemThemeProvider.java
@@ -33,6 +33,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -125,8 +126,7 @@ public class FileSystemThemeProvider implements ThemeProvider {
public Optional getThemeInfo(final String theme,
final ThemeVersion version) {
- final String themePath = createThemePath(theme, version);
- return readInfo(themePath);
+ return readInfo(theme);
}
@Override
@@ -228,15 +228,23 @@ public class FileSystemThemeProvider implements ThemeProvider {
final String path) {
final String themePath = createThemePath(theme, version);
- final String filePath = String.join(themePath, path, "/");
+ final String filePath = String.join("/", themePath, path);
try {
+ if (ccmFiles.isDirectory(filePath)) {
+ return ccmFiles
+ .listFiles(filePath)
+ .stream()
+ .map(currentPath -> buildThemeFileInfo(
+ String.join("/", theme, currentPath)))
+ .collect(Collectors.toList());
+ } else {
+ final List result = new ArrayList<>();
+ final ThemeFileInfo fileInfo = buildThemeFileInfo(filePath);
+ result.add(fileInfo);
- return ccmFiles
- .listFiles(filePath)
- .stream()
- .map(currentPath -> buildThemeFileInfo(currentPath))
- .collect(Collectors.toList());
+ return result;
+ }
} catch (FileAccessException
| FileDoesNotExistException
@@ -246,12 +254,32 @@ public class FileSystemThemeProvider implements ThemeProvider {
}
}
+ @Override
+ public Optional 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
public Optional getThemeFileAsStream(
final String theme, final ThemeVersion version, final String path) {
final String themePath = createThemePath(theme, version);
- final String filePath = String.join(themePath, path, "/");
+ final String filePath = String.join("/", themePath, path);
try {
if (ccmFiles.existsFile(path)) {
@@ -272,7 +300,7 @@ public class FileSystemThemeProvider implements ThemeProvider {
final String path) {
final String themePath = createThemePath(theme, ThemeVersion.DRAFT);
- final String filePath = String.join(themePath, path, "/");
+ final String filePath = String.join("/", themePath, path);
try {
@@ -289,7 +317,7 @@ public class FileSystemThemeProvider implements ThemeProvider {
public void deleteThemeFile(final String theme, final String path) {
final String themePath = createThemePath(theme, ThemeVersion.DRAFT);
- final String filePath = String.join(themePath, path, "/");
+ final String filePath = String.join("/", themePath, path);
try {
ccmFiles.deleteFile(filePath, true);
@@ -383,15 +411,15 @@ public class FileSystemThemeProvider implements ThemeProvider {
}
}
- private Optional readInfo(final String themePath) {
+ private Optional readInfo(final String themeName) {
final ThemeManifest manifest;
try {
final String jsonPath = String.format(
- DRAFT_THEMES_PATH + "/" + THEME_JSON, themePath);
+ DRAFT_THEMES_PATH + "/" + THEME_JSON, themeName);
final String xmlPath = String.format(
- DRAFT_THEMES_PATH + "/" + THEME_XML, themePath);
+ DRAFT_THEMES_PATH + "/" + THEME_XML, themeName);
if (ccmFiles.existsFile(jsonPath)) {
final InputStream inputStream = ccmFiles
diff --git a/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java b/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java
index b15353cbb..14d0b5663 100644
--- a/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java
+++ b/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java
@@ -256,20 +256,20 @@ public class StaticThemeProvider implements ThemeProvider {
@Override
public ThemeInfo createTheme(final String theme) {
-
+
throw new UnsupportedOperationException(String.format(
- "The ThemeProvider %s does support the creation of new themes.",
- getClass().getName()));
+ "The ThemeProvider %s does support the creation of new themes.",
+ getClass().getName()));
}
-
+
@Override
public void deleteTheme(final String theme) {
-
- throw new UnsupportedOperationException(String.format(
- "The ThemeProvider %s does support the deltion of themes.",
- getClass().getName()));
+
+ throw new UnsupportedOperationException(String.format(
+ "The ThemeProvider %s does support the deltion of themes.",
+ getClass().getName()));
}
-
+
@Override
public List listThemeFiles(final String theme,
final ThemeVersion version,
@@ -321,9 +321,37 @@ public class StaticThemeProvider implements ThemeProvider {
}
@Override
- public Optional getThemeFileAsStream(final String theme,
- final ThemeVersion version,
- final String path) {
+ public Optional getThemeFileInfo(
+ final String theme, final ThemeVersion version, final String path) {
+
+ Objects.requireNonNull(theme);
+ Objects.requireNonNull(version);
+ Objects.requireNonNull(path);
+
+ final List 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 targetFile = findFile(pathTokens,
+ currentDir);
+ if (targetFile.isPresent()) {
+ return Optional.of(generateFileInfo(targetFile.get()));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ @Override
+ public Optional getThemeFileAsStream(
+ final String theme, final ThemeVersion version, final String path) {
Objects.requireNonNull(theme);
Objects.requireNonNull(path);
@@ -390,7 +418,7 @@ public class StaticThemeProvider implements ThemeProvider {
public void publishTheme(final String theme) {
//No op in this implementation.
}
-
+
@Override
public void unpublishTheme(final String theme) {
//No op in this implementation.
diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java
index dbf920f3b..ccd4fbe5a 100644
--- a/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java
+++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java
@@ -137,6 +137,21 @@ public interface ThemeProvider extends Serializable {
ThemeVersion version,
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 getThemeFileInfo(String theme,
+ ThemeVersion version,
+ String path);
+
/**
* 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
@@ -208,11 +223,11 @@ public interface ThemeProvider extends Serializable {
* @param theme The theme to publish.
*/
void publishTheme(String theme);
-
+
/**
- * Unpublishes (deletes) the live version of a theme. For
- * implementations which do not support draft/live themes the implementation
- * of this method should be a noop, but not throw an exception.
+ * Unpublishes (deletes) the live version of a theme. For implementations
+ * which do not support draft/live themes the implementation of this method
+ * should be a noop, but not throw an exception.
*
* @param theme The theme to publish.
*/
diff --git a/ccm-core/src/main/java/org/libreccm/theming/db/DatabaseThemeProvider.java b/ccm-core/src/main/java/org/libreccm/theming/db/DatabaseThemeProvider.java
index 00d455bf7..7f6bf54c0 100644
--- a/ccm-core/src/main/java/org/libreccm/theming/db/DatabaseThemeProvider.java
+++ b/ccm-core/src/main/java/org/libreccm/theming/db/DatabaseThemeProvider.java
@@ -182,11 +182,29 @@ public class DatabaseThemeProvider implements ThemeProvider {
return result;
}
+ @Override
+ public Optional 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 = fileRepository
+ .findByPath(theme, path, version);
+
+ if (themeFile.isPresent()) {
+ return Optional.of(createThemeFileInfo(themeFile.get()));
+ } else {
+ return Optional.empty();
+ }
+ }
+
@Override
@Transactional(Transactional.TxType.REQUIRED)
- public Optional getThemeFileAsStream(final String themeName,
- final ThemeVersion version,
- final String path) {
+ public Optional getThemeFileAsStream(
+ final String themeName, final ThemeVersion version, final String path) {
final Optional theme = themeRepository
.findThemeByName(themeName, version);
@@ -325,10 +343,10 @@ public class DatabaseThemeProvider implements ThemeProvider {
.findThemeByName(themeName, ThemeVersion.DRAFT)
.ifPresent(themeManager::publishTheme);
}
-
+
@Override
public void unpublishTheme(final String themeName) {
-
+
themeRepository
.findThemeByName(themeName, ThemeVersion.LIVE)
.ifPresent(themeManager::unpublishTheme);
diff --git a/ccm-core/src/main/java/org/libreccm/theming/manager/Themes.java b/ccm-core/src/main/java/org/libreccm/theming/manager/Themes.java
index bb3e88576..f5a948871 100644
--- a/ccm-core/src/main/java/org/libreccm/theming/manager/Themes.java
+++ b/ccm-core/src/main/java/org/libreccm/theming/manager/Themes.java
@@ -20,12 +20,16 @@ package org.libreccm.theming.manager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
+import org.libreccm.theming.ThemeFileInfo;
import org.libreccm.theming.ThemeInfo;
import org.libreccm.theming.ThemeProvider;
import org.libreccm.theming.ThemeVersion;
import org.libreccm.theming.ThemingPrivileges;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
@@ -52,7 +56,6 @@ import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-
/**
*
* @author Jens Pelzetter
@@ -75,7 +78,7 @@ public class Themes implements Serializable {
@GET
@Path("/providers")
@Produces(MediaType.APPLICATION_JSON)
- //@AuthorizationRequired
+ @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public String getThemeProviders() {
@@ -103,7 +106,7 @@ public class Themes implements Serializable {
@GET
@Path("/themes")
@Produces(MediaType.APPLICATION_JSON)
- //@AuthorizationRequired
+ @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public List getAvailableThemes() {
@@ -120,7 +123,7 @@ public class Themes implements Serializable {
@GET
@Path("/themes/{theme}")
@Produces(MediaType.APPLICATION_JSON)
- //@AuthorizationRequired
+ @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.EDIT_THEME)
public ThemeInfo getTheme(@PathParam("theme") final String themeName) {
@@ -140,7 +143,7 @@ public class Themes implements Serializable {
@Path("/themes/{theme}")
@Produces(MediaType.APPLICATION_JSON)
@SuppressWarnings("unchecked")
- //@AuthorizationRequired
+ @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public ThemeInfo createTheme(
@PathParam("theme") final String themeName,
@@ -176,7 +179,7 @@ public class Themes implements Serializable {
@DELETE
@Path("/themes/{theme}")
- //@AuthorizationRequired
+ @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public void deleteTheme(@PathParam("theme") final String themeName) {
@@ -195,7 +198,7 @@ public class Themes implements Serializable {
@POST
@Path("/themes/{theme}/live")
- //@AuthorizationRequired
+ @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public void publishTheme(@PathParam("theme") final String themeName) {
@@ -213,7 +216,7 @@ public class Themes implements Serializable {
@DELETE
@Path("/themes/{theme}/live")
- //@AuthorizationRequired
+ @AuthorizationRequired
@RequiresPrivilege(ThemingPrivileges.ADMINISTER_THEMES)
public void unPublishTheme(@PathParam("theme") final String themeName) {
@@ -226,7 +229,134 @@ public class Themes implements Serializable {
} else {
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 provider = findProvider(themeName);
+
+ if (provider.isPresent()) {
+
+ final Optional fileInfo = provider
+ .get()
+ .getThemeFileInfo(themeName, ThemeVersion.DRAFT, "/");
+
+ if (fileInfo.isPresent()) {
+
+ if (fileInfo.get().isDirectory()) {
+
+ final List 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 provider = findProvider(themeName);
+
+ if (provider.isPresent()) {
+
+ final Optional fileInfo = provider
+ .get()
+ .getThemeFileInfo(themeName, ThemeVersion.DRAFT, path);
+
+ if (fileInfo.isPresent()) {
+
+ final ThemeFileInfo themeFileInfo = fileInfo.get();
+
+ if (themeFileInfo.isDirectory()) {
+
+ final List fileInfos = provider
+ .get()
+ .listThemeFiles(themeName, ThemeVersion.DRAFT, path);
+
+ return Response
+ .ok(fileInfos)
+ .type(MediaType.APPLICATION_JSON)
+ .build();
+
+ } else {
+ final Optional 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) {
diff --git a/pom.xml b/pom.xml
index c7c89dea2..41d7e108f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -462,6 +462,23 @@
shiro-web
1.3.2
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.10.5
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.10.5
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.10.5
+