CcmNG: Backend for Theme editing
git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5693 8810af33-2d31-482b-a856-94f89814c4df
parent
db9e9b9cfd
commit
5880af452e
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
17
pom.xml
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue