Allow projects to specify themes
Gerrit administrators can put per-project themes in $site_path/themes/{project-name}; the same header/footer/CSS filenames are supported as for sitewide themes. These are inherited and cached per-project and exposed via GET /projects/X/config. Themes must be provided by a site admin rather than arbitrary project admins, so the site admins can be responsible for making sure themes do not introduce XSS vulnerabilities. Change-Id: I065d9e6d4df9275b963bb142ec11f66b5604678b
This commit is contained in:
@@ -24,6 +24,10 @@ import java.io.IOException;
|
||||
/** Important paths within a {@link SitePath}. */
|
||||
@Singleton
|
||||
public final class SitePaths {
|
||||
public static final String CSS_FILENAME = "GerritSite.css";
|
||||
public static final String HEADER_FILENAME = "GerritSiteHeader.html";
|
||||
public static final String FOOTER_FILENAME = "GerritSiteFooter.html";
|
||||
|
||||
public final File site_path;
|
||||
public final File bin_dir;
|
||||
public final File etc_dir;
|
||||
@@ -35,6 +39,7 @@ public final class SitePaths {
|
||||
public final File mail_dir;
|
||||
public final File hooks_dir;
|
||||
public final File static_dir;
|
||||
public final File themes_dir;
|
||||
|
||||
public final File gerrit_sh;
|
||||
public final File gerrit_war;
|
||||
@@ -71,6 +76,7 @@ public final class SitePaths {
|
||||
mail_dir = new File(etc_dir, "mail");
|
||||
hooks_dir = new File(site_path, "hooks");
|
||||
static_dir = new File(site_path, "static");
|
||||
themes_dir = new File(site_path, "themes");
|
||||
|
||||
gerrit_sh = new File(bin_dir, "gerrit.sh");
|
||||
gerrit_war = new File(bin_dir, "gerrit.war");
|
||||
@@ -85,9 +91,9 @@ public final class SitePaths {
|
||||
ssh_dsa = new File(etc_dir, "ssh_host_dsa_key");
|
||||
peer_keys = new File(etc_dir, "peer_keys");
|
||||
|
||||
site_css = new File(etc_dir, "GerritSite.css");
|
||||
site_header = new File(etc_dir, "GerritSiteHeader.html");
|
||||
site_footer = new File(etc_dir, "GerritSiteFooter.html");
|
||||
site_css = new File(etc_dir, CSS_FILENAME);
|
||||
site_header = new File(etc_dir, HEADER_FILENAME);
|
||||
site_footer = new File(etc_dir, FOOTER_FILENAME);
|
||||
site_gitweb = new File(etc_dir, "gitweb_config.perl");
|
||||
|
||||
if (site_path.exists()) {
|
||||
|
@@ -30,6 +30,7 @@ public class GetConfig implements RestReadView<ProjectResource> {
|
||||
public Boolean requireChangeId;
|
||||
|
||||
public Map<String, CommentLinkInfo> commentlinks;
|
||||
public ThemeInfo theme;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,6 +52,9 @@ public class GetConfig implements RestReadView<ProjectResource> {
|
||||
for (CommentLinkInfo cl : project.getCommentLinks()) {
|
||||
result.commentlinks.put(cl.name, cl);
|
||||
}
|
||||
|
||||
// Themes are visible to anyone, as they are rendered client-side.
|
||||
result.theme = project.getTheme();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@@ -14,12 +14,14 @@
|
||||
|
||||
package com.google.gerrit.server.project;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gerrit.common.data.AccessSection;
|
||||
import com.google.gerrit.common.data.GroupReference;
|
||||
import com.google.gerrit.common.data.LabelType;
|
||||
@@ -35,6 +37,7 @@ import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.account.CapabilityCollection;
|
||||
import com.google.gerrit.server.account.GroupMembership;
|
||||
import com.google.gerrit.server.config.AllProjectsName;
|
||||
import com.google.gerrit.server.config.SitePaths;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.ProjectConfig;
|
||||
import com.google.inject.Inject;
|
||||
@@ -45,7 +48,10 @@ import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
|
||||
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -59,11 +65,15 @@ import java.util.Set;
|
||||
|
||||
/** Cached information on a project. */
|
||||
public class ProjectState {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(ProjectState.class);
|
||||
|
||||
public interface Factory {
|
||||
ProjectState create(ProjectConfig config);
|
||||
}
|
||||
|
||||
private final boolean isAllProjects;
|
||||
private final SitePaths sitePaths;
|
||||
private final AllProjectsName allProjectsName;
|
||||
private final ProjectCache projectCache;
|
||||
private final ProjectControl.AssistedFactory projectControlFactory;
|
||||
@@ -84,11 +94,15 @@ public class ProjectState {
|
||||
/** Local access sections, wrapped in SectionMatchers for faster evaluation. */
|
||||
private volatile List<SectionMatcher> localAccessSections;
|
||||
|
||||
/** Theme information loaded from site_path/themes. */
|
||||
private volatile ThemeInfo theme;
|
||||
|
||||
/** If this is all projects, the capabilities used by the server. */
|
||||
private final CapabilityCollection capabilities;
|
||||
|
||||
@Inject
|
||||
public ProjectState(
|
||||
final SitePaths sitePaths,
|
||||
final ProjectCache projectCache,
|
||||
final AllProjectsName allProjectsName,
|
||||
final ProjectControl.AssistedFactory projectControlFactory,
|
||||
@@ -97,6 +111,7 @@ public class ProjectState {
|
||||
final RulesCache rulesCache,
|
||||
final List<CommentLinkInfo> commentLinks,
|
||||
@Assisted final ProjectConfig config) {
|
||||
this.sitePaths = sitePaths;
|
||||
this.projectCache = projectCache;
|
||||
this.isAllProjects = config.getProject().getNameKey().equals(allProjectsName);
|
||||
this.allProjectsName = allProjectsName;
|
||||
@@ -397,6 +412,47 @@ public class ProjectState {
|
||||
return ImmutableList.copyOf(cls.values());
|
||||
}
|
||||
|
||||
public ThemeInfo getTheme() {
|
||||
ThemeInfo theme = this.theme;
|
||||
if (theme == null) {
|
||||
synchronized (this) {
|
||||
theme = this.theme;
|
||||
if (theme == null) {
|
||||
theme = loadTheme();
|
||||
this.theme = theme;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (theme == ThemeInfo.INHERIT) {
|
||||
ProjectState parent = Iterables.getFirst(parents(), null);
|
||||
return parent != null ? parent.getTheme() : null;
|
||||
}
|
||||
return theme;
|
||||
}
|
||||
|
||||
private ThemeInfo loadTheme() {
|
||||
String name = getConfig().getProject().getName();
|
||||
File dir = new File(sitePaths.themes_dir, name);
|
||||
if (!dir.exists()) {
|
||||
return ThemeInfo.INHERIT;
|
||||
} else if (!dir.isDirectory()) {
|
||||
log.warn("Bad theme for {}: not a directory", name);
|
||||
return ThemeInfo.INHERIT;
|
||||
}
|
||||
try {
|
||||
return new ThemeInfo(readFile(new File(dir, SitePaths.CSS_FILENAME)),
|
||||
readFile(new File(dir, SitePaths.HEADER_FILENAME)),
|
||||
readFile(new File(dir, SitePaths.FOOTER_FILENAME)));
|
||||
} catch (IOException e) {
|
||||
log.error("Error reading theme for " + name, e);
|
||||
return ThemeInfo.INHERIT;
|
||||
}
|
||||
}
|
||||
|
||||
private String readFile(File f) throws IOException {
|
||||
return f.exists() ? Files.toString(f, Charsets.UTF_8) : null;
|
||||
}
|
||||
|
||||
private boolean getInheritableBoolean(Function<Project, InheritableBoolean> func) {
|
||||
for (ProjectState s : tree()) {
|
||||
switch (func.apply(s.getProject())) {
|
||||
|
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.package com.google.gerrit.server.git;
|
||||
|
||||
package com.google.gerrit.server.project;
|
||||
|
||||
public class ThemeInfo {
|
||||
static final ThemeInfo INHERIT = new ThemeInfo(null, null, null);
|
||||
|
||||
public final String css;
|
||||
public final String header;
|
||||
public final String footer;
|
||||
|
||||
ThemeInfo(String css, String header, String footer) {
|
||||
this.css = css;
|
||||
this.header = header;
|
||||
this.footer = footer;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user