From 5880af452e451c4192308b0bfec0ac1d02f21d1e Mon Sep 17 00:00:00 2001 From: jensp Date: Wed, 19 Sep 2018 18:14:14 +0000 Subject: [PATCH] CcmNG: Backend for Theme editing git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5693 8810af33-2d31-482b-a856-94f89814c4df --- ccm-core/pom.xml | 13 ++ .../com/arsdigita/kernel/KernelConfig.java | 18 +- .../main/java/org/libreccm/core/CcmCore.java | 3 + .../java/org/libreccm/files/CcmFiles.java | 5 +- .../libreccm/files/NIOFileSystemAdapter.java | 3 +- .../security/CcmJwtAuthentication.java | 43 +++++ .../org/libreccm/security/JwtProvider.java | 158 ++++++++++++++++++ .../theming/FileSystemThemeProvider.java | 56 +++++-- .../libreccm/theming/StaticThemeProvider.java | 54 ++++-- .../org/libreccm/theming/ThemeProvider.java | 23 ++- .../theming/db/DatabaseThemeProvider.java | 28 +++- .../org/libreccm/theming/manager/Themes.java | 146 +++++++++++++++- pom.xml | 17 ++ 13 files changed, 520 insertions(+), 47 deletions(-) create mode 100644 ccm-core/src/main/java/org/libreccm/security/CcmJwtAuthentication.java create mode 100644 ccm-core/src/main/java/org/libreccm/security/JwtProvider.java 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 +